@nile-squad/nylonpay-ts 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -35,14 +35,13 @@ payment.on("failed", ({ error }) => notifyCustomer(error));
35
35
 
36
36
  ## Configuration
37
37
 
38
- > Test vs. live mode is selected by your API key a sandbox key routes to test
39
- > providers, a live key processes real money. There is no `environment` option.
38
+ Use your test keys to work in sandbox, or your production keys to go live. There is no separate `environment` optionthe key determines the mode.
40
39
 
41
40
  | Option | Required | Default | Description |
42
41
  |---|---|---|---|
43
42
  | `apiKey` | Yes | | Must start with `npk_` |
44
43
  | `apiSecret` | Yes | | Must start with `nps_` |
45
- | `baseUrl` | No | `https://api.nylonpay.io/api/services` | API endpoint |
44
+ | `baseUrl` | No | Default is used | Override only if self-hosting |
46
45
  | `timeoutMs` | No | `30000` | Request timeout in milliseconds |
47
46
  | `maxRetries` | No | `3` | Retry count for failed requests |
48
47
  | `maxPollIntervalMs` | No | `2000` | Polling interval for async payments |
@@ -170,7 +169,7 @@ app.post("/webhooks", (req, res) => {
170
169
  const isValid = nylonpay.verifyWebhookSignature({
171
170
  payload: req.rawBody,
172
171
  signature: req.headers["x-nylon-signature"],
173
- secret: process.env.NYLONPAY_WEBHOOK_SECRET,
172
+ secret: "nps_...",
174
173
  });
175
174
 
176
175
  if (!isValid) return res.status(401).send("Invalid signature");
package/dist/index.cjs CHANGED
@@ -15,7 +15,7 @@ function createEmitter() {
15
15
  if (!state.listeners.has(event)) {
16
16
  state.listeners.set(event, /* @__PURE__ */ new Set());
17
17
  }
18
- state.listeners.get(event).add(handler);
18
+ state.listeners.get(event)?.add(handler);
19
19
  return () => off(event, handler);
20
20
  }
21
21
  function once(event, handler) {
@@ -55,217 +55,24 @@ function createEmitter() {
55
55
  const emitter = { on, once, off, emit, clear, listenerCount };
56
56
  return emitter;
57
57
  }
58
-
59
- // src/payment.ts
60
- var STATUS_TO_EVENT = {
61
- successful: "success",
62
- failed: "failed",
63
- processing: "processing",
64
- cancelled: "cancelled"
65
- };
66
- function statusToEvent(status) {
67
- return STATUS_TO_EVENT[status] ?? null;
58
+ function generateFingerprint() {
59
+ const components = [
60
+ `type:${os.type()}`,
61
+ `platform:${os.platform()}`,
62
+ `arch:${os.arch()}`,
63
+ `release:${os.release()}`,
64
+ `hostname:${os.hostname()}`,
65
+ `node:${process.versions.node}`,
66
+ `v8:${process.versions.v8}`
67
+ ].join("|");
68
+ return crypto.createHash("sha256").update(components).digest("hex");
68
69
  }
69
- var TERMINAL_STATES = /* @__PURE__ */ new Set([
70
- "successful",
71
- "failed",
72
- "cancelled"
73
- ]);
74
- function createPaymentInstance(initialResponse, deps) {
75
- const state = {
76
- reference: initialResponse.reference,
77
- status: initialResponse.status,
78
- transaction: null,
79
- pollingTimer: null,
80
- resolved: false,
81
- pollAttempts: 0,
82
- pollStartTime: Date.now(),
83
- emitter: createEmitter(),
84
- fetchStatus: deps.fetchStatus,
85
- fetchTransaction: deps.fetchTransaction,
86
- pollIntervalMs: deps.pollIntervalMs ?? 2e3,
87
- maxPollDuration: deps.maxPollDuration ?? 3e5,
88
- maxPollAttempts: deps.maxPollAttempts ?? 150
89
- };
90
- function resolveWithError(error) {
91
- state.resolved = true;
92
- stopPolling();
93
- emitEvent("error", error);
94
- }
95
- function emitEvent(event, error) {
96
- const data = {
97
- event,
98
- transaction: state.transaction ?? void 0,
99
- error,
100
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
101
- };
102
- state.emitter.emit(event, data);
103
- }
104
- async function handleTerminalState(status) {
105
- const txResult = await state.fetchTransaction({
106
- reference: state.reference
107
- });
108
- if (txResult.isOk) {
109
- state.transaction = txResult.value;
110
- const event = statusToEvent(status);
111
- if (event) {
112
- emitEvent(event);
113
- }
114
- } else {
115
- emitEvent("error", `Failed to fetch transaction: ${txResult.error}`);
116
- }
117
- state.resolved = true;
118
- stopPolling();
119
- }
120
- async function handleStatusUpdate(response) {
121
- if (response.reference !== state.reference) {
122
- resolveWithError(
123
- `Reference mismatch: expected ${state.reference} but got ${response.reference}`
124
- );
125
- return;
126
- }
127
- const newStatus = response.status;
128
- const oldStatus = state.status;
129
- state.status = newStatus;
130
- if (newStatus !== oldStatus) {
131
- const event = statusToEvent(newStatus);
132
- if (event) {
133
- if (TERMINAL_STATES.has(newStatus)) {
134
- await handleTerminalState(newStatus);
135
- return;
136
- }
137
- emitEvent(event);
138
- }
139
- }
140
- }
141
- function handlePollError(error) {
142
- const isNotFound = error.includes("not found") || error.includes("NOT_FOUND");
143
- if (isNotFound) {
144
- return;
145
- }
146
- emitEvent("error", error);
147
- state.resolved = true;
148
- stopPolling();
149
- }
150
- function scheduleNextPoll() {
151
- if (state.resolved || state.pollingTimer) {
152
- return;
153
- }
154
- state.pollingTimer = setTimeout(() => {
155
- state.pollingTimer = null;
156
- void pollStatus();
157
- }, state.pollIntervalMs);
158
- }
159
- async function pollStatus() {
160
- if (state.resolved) {
161
- stopPolling();
162
- return;
163
- }
164
- if (state.pollAttempts >= state.maxPollAttempts) {
165
- resolveWithError("Polling timeout: exceeded maximum attempts");
166
- return;
167
- }
168
- if (Date.now() - state.pollStartTime >= state.maxPollDuration) {
169
- resolveWithError("Polling timeout: exceeded maximum duration");
170
- return;
171
- }
172
- state.pollAttempts += 1;
173
- const result = await state.fetchStatus({ reference: state.reference });
174
- if (result.isOk) {
175
- await handleStatusUpdate(result.value);
176
- } else {
177
- handlePollError(result.error);
178
- }
179
- if (state.resolved) {
180
- stopPolling();
181
- return;
182
- }
183
- scheduleNextPoll();
184
- }
185
- function startPolling() {
186
- scheduleNextPoll();
187
- }
188
- function stopPolling() {
189
- if (state.pollingTimer) {
190
- clearTimeout(state.pollingTimer);
191
- state.pollingTimer = null;
192
- }
193
- }
194
- function on(event, handler) {
195
- state.emitter.on(event, handler);
196
- return paymentInstance;
197
- }
198
- function off(event, handler) {
199
- state.emitter.off(event, handler);
200
- return paymentInstance;
201
- }
202
- function once(event, handler) {
203
- state.emitter.once(event, handler);
204
- return paymentInstance;
205
- }
206
- function wait() {
207
- return new Promise((resolve, reject) => {
208
- if (state.resolved) {
209
- if (state.status === "successful" && state.transaction) {
210
- resolve(state.transaction);
211
- } else {
212
- reject(new Error(`Payment ${state.status ?? "error"}`));
213
- }
214
- return;
215
- }
216
- function onSuccess() {
217
- cleanup();
218
- if (state.transaction) {
219
- resolve(state.transaction);
220
- } else {
221
- reject(
222
- new Error("Payment successful but transaction data unavailable")
223
- );
224
- }
225
- }
226
- function onFailed() {
227
- cleanup();
228
- reject(new Error("Payment failed"));
229
- }
230
- function onCancelled() {
231
- cleanup();
232
- reject(new Error("Payment cancelled"));
233
- }
234
- function onError(data) {
235
- cleanup();
236
- const eventData = data;
237
- reject(new Error(eventData.error ?? "Payment error"));
238
- }
239
- function cleanup() {
240
- state.emitter.off("success", onSuccess);
241
- state.emitter.off("failed", onFailed);
242
- state.emitter.off("cancelled", onCancelled);
243
- state.emitter.off("error", onError);
244
- }
245
- state.emitter.on("success", onSuccess);
246
- state.emitter.on("failed", onFailed);
247
- state.emitter.on("cancelled", onCancelled);
248
- state.emitter.on("error", onError);
249
- });
250
- }
251
- const paymentInstance = {
252
- get reference() {
253
- return state.reference;
254
- },
255
- get status() {
256
- return state.status;
257
- },
258
- on,
259
- once,
260
- off,
261
- wait
262
- };
263
- startPolling();
264
- return paymentInstance;
70
+ function generateNonce(length = 16) {
71
+ return crypto.randomBytes(length).toString("hex");
265
72
  }
266
73
 
267
74
  // src/sdk.config.ts
268
- var DEFAULT_BASE_URL = "https://api.nylonpay.io/api/services";
75
+ var DEFAULT_BASE_URL = "https://api.nylonpay.nilesquad.com/api/services";
269
76
  var DEFAULT_TIMEOUT_MS = 3e4;
270
77
  var DEFAULT_MAX_RETRIES = 3;
271
78
  var DEFAULT_MAX_POLL_INTERVAL_MS = 2e3;
@@ -283,21 +90,6 @@ var SDK_ACTIONS = {
283
90
  createInvoice: "sdk-create-invoice"
284
91
  };
285
92
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
286
- function generateFingerprint() {
287
- const components = [
288
- `type:${os.type()}`,
289
- `platform:${os.platform()}`,
290
- `arch:${os.arch()}`,
291
- `release:${os.release()}`,
292
- `hostname:${os.hostname()}`,
293
- `node:${process.versions.node}`,
294
- `v8:${process.versions.v8}`
295
- ].join("|");
296
- return crypto.createHash("sha256").update(components).digest("hex");
297
- }
298
- function generateNonce(length = 16) {
299
- return crypto.randomBytes(length).toString("hex");
300
- }
301
93
  function sortValue(value) {
302
94
  if (Array.isArray(value)) {
303
95
  return value.map((entry) => sortValue(entry));
@@ -517,6 +309,214 @@ function parseError(error) {
517
309
  }
518
310
  return { code: "UNKNOWN", message: error };
519
311
  }
312
+
313
+ // src/payment.ts
314
+ var STATUS_TO_EVENT = {
315
+ successful: "success",
316
+ failed: "failed",
317
+ processing: "processing",
318
+ cancelled: "cancelled"
319
+ };
320
+ function statusToEvent(status) {
321
+ return STATUS_TO_EVENT[status] ?? null;
322
+ }
323
+ var TERMINAL_STATES = /* @__PURE__ */ new Set([
324
+ "successful",
325
+ "failed",
326
+ "cancelled"
327
+ ]);
328
+ function createPaymentInstance(initialResponse, deps) {
329
+ const state = {
330
+ reference: initialResponse.reference,
331
+ status: initialResponse.status,
332
+ transaction: null,
333
+ pollingTimer: null,
334
+ resolved: false,
335
+ pollAttempts: 0,
336
+ pollStartTime: Date.now(),
337
+ emitter: createEmitter(),
338
+ fetchStatus: deps.fetchStatus,
339
+ fetchTransaction: deps.fetchTransaction,
340
+ pollIntervalMs: deps.pollIntervalMs ?? 2e3,
341
+ maxPollDuration: deps.maxPollDuration ?? 3e5,
342
+ maxPollAttempts: deps.maxPollAttempts ?? 150
343
+ };
344
+ function resolveWithError(error) {
345
+ state.resolved = true;
346
+ stopPolling();
347
+ emitEvent("error", parseError(error).message);
348
+ }
349
+ function emitEvent(event, error) {
350
+ const data = {
351
+ event,
352
+ transaction: state.transaction ?? void 0,
353
+ error,
354
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
355
+ };
356
+ state.emitter.emit(event, data);
357
+ }
358
+ async function handleTerminalState(status) {
359
+ const txResult = await state.fetchTransaction({
360
+ reference: state.reference
361
+ });
362
+ if (txResult.isOk) {
363
+ state.transaction = txResult.value;
364
+ const event = statusToEvent(status);
365
+ if (event) {
366
+ emitEvent(event);
367
+ }
368
+ } else {
369
+ emitEvent("error", `Failed to fetch transaction: ${txResult.error}`);
370
+ }
371
+ state.resolved = true;
372
+ stopPolling();
373
+ }
374
+ async function handleStatusUpdate(response) {
375
+ if (response.reference !== state.reference) {
376
+ resolveWithError(
377
+ `Reference mismatch: expected ${state.reference} but got ${response.reference}`
378
+ );
379
+ return;
380
+ }
381
+ const newStatus = response.status;
382
+ const oldStatus = state.status;
383
+ state.status = newStatus;
384
+ if (newStatus !== oldStatus) {
385
+ const event = statusToEvent(newStatus);
386
+ if (event) {
387
+ if (TERMINAL_STATES.has(newStatus)) {
388
+ await handleTerminalState(newStatus);
389
+ return;
390
+ }
391
+ emitEvent(event);
392
+ }
393
+ }
394
+ }
395
+ function handlePollError(error) {
396
+ const isNotFound = error.includes("not found") || error.includes("NOT_FOUND");
397
+ if (isNotFound) {
398
+ return;
399
+ }
400
+ emitEvent("error", parseError(error).message);
401
+ state.resolved = true;
402
+ stopPolling();
403
+ }
404
+ function scheduleNextPoll() {
405
+ if (state.resolved || state.pollingTimer) {
406
+ return;
407
+ }
408
+ state.pollingTimer = setTimeout(() => {
409
+ state.pollingTimer = null;
410
+ void pollStatus();
411
+ }, state.pollIntervalMs);
412
+ }
413
+ async function pollStatus() {
414
+ if (state.resolved) {
415
+ stopPolling();
416
+ return;
417
+ }
418
+ if (state.pollAttempts >= state.maxPollAttempts) {
419
+ resolveWithError("Polling timeout: exceeded maximum attempts");
420
+ return;
421
+ }
422
+ if (Date.now() - state.pollStartTime >= state.maxPollDuration) {
423
+ resolveWithError("Polling timeout: exceeded maximum duration");
424
+ return;
425
+ }
426
+ state.pollAttempts += 1;
427
+ const result = await state.fetchStatus({ reference: state.reference });
428
+ if (result.isOk) {
429
+ await handleStatusUpdate(result.value);
430
+ } else {
431
+ handlePollError(result.error);
432
+ }
433
+ if (state.resolved) {
434
+ stopPolling();
435
+ return;
436
+ }
437
+ scheduleNextPoll();
438
+ }
439
+ function startPolling() {
440
+ scheduleNextPoll();
441
+ }
442
+ function stopPolling() {
443
+ if (state.pollingTimer) {
444
+ clearTimeout(state.pollingTimer);
445
+ state.pollingTimer = null;
446
+ }
447
+ }
448
+ function on(event, handler) {
449
+ state.emitter.on(event, handler);
450
+ return paymentInstance;
451
+ }
452
+ function off(event, handler) {
453
+ state.emitter.off(event, handler);
454
+ return paymentInstance;
455
+ }
456
+ function once(event, handler) {
457
+ state.emitter.once(event, handler);
458
+ return paymentInstance;
459
+ }
460
+ function wait() {
461
+ return new Promise((resolve, reject) => {
462
+ if (state.resolved) {
463
+ if (state.status === "successful" && state.transaction) {
464
+ resolve(state.transaction);
465
+ } else {
466
+ reject(new Error(`Payment ${state.status ?? "error"}`));
467
+ }
468
+ return;
469
+ }
470
+ function onSuccess() {
471
+ cleanup();
472
+ if (state.transaction) {
473
+ resolve(state.transaction);
474
+ } else {
475
+ reject(
476
+ new Error("Payment successful but transaction data unavailable")
477
+ );
478
+ }
479
+ }
480
+ function onFailed() {
481
+ cleanup();
482
+ reject(new Error("Payment failed"));
483
+ }
484
+ function onCancelled() {
485
+ cleanup();
486
+ reject(new Error("Payment cancelled"));
487
+ }
488
+ function onError(data) {
489
+ cleanup();
490
+ const eventData = data;
491
+ reject(new Error(eventData.error ?? "Payment error"));
492
+ }
493
+ function cleanup() {
494
+ state.emitter.off("success", onSuccess);
495
+ state.emitter.off("failed", onFailed);
496
+ state.emitter.off("cancelled", onCancelled);
497
+ state.emitter.off("error", onError);
498
+ }
499
+ state.emitter.on("success", onSuccess);
500
+ state.emitter.on("failed", onFailed);
501
+ state.emitter.on("cancelled", onCancelled);
502
+ state.emitter.on("error", onError);
503
+ });
504
+ }
505
+ const paymentInstance = {
506
+ get reference() {
507
+ return state.reference;
508
+ },
509
+ get status() {
510
+ return state.status;
511
+ },
512
+ on,
513
+ once,
514
+ off,
515
+ wait
516
+ };
517
+ startPolling();
518
+ return paymentInstance;
519
+ }
520
520
  function verifyWebhookSignature(input) {
521
521
  const payloadBytes = typeof input.payload === "string" ? Buffer.from(input.payload, "utf8") : Buffer.from(input.payload);
522
522
  const expectedSignature = crypto.createHmac("sha256", input.secret).update(payloadBytes).digest("hex");