@nile-squad/nylonpay-ts 1.0.0 → 1.0.2
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 +17 -5
- package/dist/index.cjs +296 -230
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -3
- package/dist/index.d.ts +57 -3
- package/dist/index.js +296 -230
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -35,14 +35,13 @@ payment.on("failed", ({ error }) => notifyCustomer(error));
|
|
|
35
35
|
|
|
36
36
|
## Configuration
|
|
37
37
|
|
|
38
|
-
|
|
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` option — the 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 |
|
|
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:
|
|
172
|
+
secret: "nps_...",
|
|
174
173
|
});
|
|
175
174
|
|
|
176
175
|
if (!isValid) return res.status(401).send("Invalid signature");
|
|
@@ -197,6 +196,19 @@ payment.off("success", handler);
|
|
|
197
196
|
const tx = await payment.wait();
|
|
198
197
|
```
|
|
199
198
|
|
|
199
|
+
Use `safeTry` from `slang-ts` to handle the promise without try/catch:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { safeTry } from "slang-ts";
|
|
203
|
+
|
|
204
|
+
const result = await safeTry(() => payment.wait());
|
|
205
|
+
if (result.isOk) {
|
|
206
|
+
console.log("paid:", result.value.reference);
|
|
207
|
+
} else {
|
|
208
|
+
console.log("failed or timed out:", result.error);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
200
212
|
## Error Handling
|
|
201
213
|
|
|
202
214
|
All operations return `Result<T, string>` from [slang-ts](https://github.com/nile-squad/slang-ts). Use `parseError` to get structured error objects.
|
|
@@ -208,7 +220,7 @@ const result = await nylonpay.getStatus({ reference: "ORDER-123" });
|
|
|
208
220
|
if (!result.isOk) {
|
|
209
221
|
const error = parseError(result.error);
|
|
210
222
|
if (error.retryable) {
|
|
211
|
-
//
|
|
223
|
+
// retry
|
|
212
224
|
}
|
|
213
225
|
}
|
|
214
226
|
```
|
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)
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
70
|
-
"
|
|
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.
|
|
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");
|
|
@@ -573,11 +573,25 @@ function createSdkInstance(config) {
|
|
|
573
573
|
if (input.method === "bank" && !input.bank) {
|
|
574
574
|
throw new Error('bank details are required when method is "bank"');
|
|
575
575
|
}
|
|
576
|
-
|
|
576
|
+
let payload = { ...input, reference };
|
|
577
|
+
if (config.hooks?.beforeCollect) {
|
|
578
|
+
const mutated = await config.hooks.beforeCollect(payload);
|
|
579
|
+
if (mutated != null)
|
|
580
|
+
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
581
|
+
}
|
|
577
582
|
const result = await transport.send({
|
|
578
583
|
action: SDK_ACTIONS.collectPayment,
|
|
579
584
|
payload
|
|
580
585
|
});
|
|
586
|
+
if (config.hooks?.afterCollect) {
|
|
587
|
+
await config.hooks.afterCollect(
|
|
588
|
+
result.isOk ? slangTs.Ok({
|
|
589
|
+
reference: result.value.reference,
|
|
590
|
+
status: result.value.status
|
|
591
|
+
}) : slangTs.Err(result.error),
|
|
592
|
+
payload
|
|
593
|
+
);
|
|
594
|
+
}
|
|
581
595
|
if (result.isOk) {
|
|
582
596
|
return createPaymentInstance(result.value, commonDeps);
|
|
583
597
|
}
|
|
@@ -601,11 +615,25 @@ function createSdkInstance(config) {
|
|
|
601
615
|
if (input.method === "bank" && !input.bank) {
|
|
602
616
|
throw new Error('bank details are required when method is "bank"');
|
|
603
617
|
}
|
|
604
|
-
|
|
618
|
+
let payload = { ...input, reference };
|
|
619
|
+
if (config.hooks?.beforeCollect) {
|
|
620
|
+
const mutated = await config.hooks.beforeCollect(payload);
|
|
621
|
+
if (mutated != null)
|
|
622
|
+
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
623
|
+
}
|
|
605
624
|
const result = await transport.send({
|
|
606
625
|
action: SDK_ACTIONS.collectPaymentAndResolve,
|
|
607
626
|
payload
|
|
608
627
|
});
|
|
628
|
+
if (config.hooks?.afterCollect) {
|
|
629
|
+
await config.hooks.afterCollect(
|
|
630
|
+
result.isOk ? slangTs.Ok({
|
|
631
|
+
reference: result.value.reference,
|
|
632
|
+
status: result.value.status
|
|
633
|
+
}) : slangTs.Err(result.error),
|
|
634
|
+
payload
|
|
635
|
+
);
|
|
636
|
+
}
|
|
609
637
|
if (result.isOk) {
|
|
610
638
|
return slangTs.Ok(result.value);
|
|
611
639
|
}
|
|
@@ -625,11 +653,25 @@ function createSdkInstance(config) {
|
|
|
625
653
|
input.destination.accountNumber,
|
|
626
654
|
"destination.accountNumber"
|
|
627
655
|
);
|
|
628
|
-
|
|
656
|
+
let payload = { ...input, reference };
|
|
657
|
+
if (config.hooks?.beforePayout) {
|
|
658
|
+
const mutated = await config.hooks.beforePayout(payload);
|
|
659
|
+
if (mutated != null)
|
|
660
|
+
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
661
|
+
}
|
|
629
662
|
const result = await transport.send({
|
|
630
663
|
action: SDK_ACTIONS.makePayout,
|
|
631
664
|
payload
|
|
632
665
|
});
|
|
666
|
+
if (config.hooks?.afterPayout) {
|
|
667
|
+
await config.hooks.afterPayout(
|
|
668
|
+
result.isOk ? slangTs.Ok({
|
|
669
|
+
reference: result.value.reference,
|
|
670
|
+
status: result.value.status
|
|
671
|
+
}) : slangTs.Err(result.error),
|
|
672
|
+
payload
|
|
673
|
+
);
|
|
674
|
+
}
|
|
633
675
|
if (result.isOk) {
|
|
634
676
|
return createPaymentInstance(result.value, commonDeps);
|
|
635
677
|
}
|
|
@@ -658,11 +700,25 @@ function createSdkInstance(config) {
|
|
|
658
700
|
input.destination.accountNumber,
|
|
659
701
|
"destination.accountNumber"
|
|
660
702
|
);
|
|
661
|
-
|
|
703
|
+
let payload = { ...input, reference };
|
|
704
|
+
if (config.hooks?.beforePayout) {
|
|
705
|
+
const mutated = await config.hooks.beforePayout(payload);
|
|
706
|
+
if (mutated != null)
|
|
707
|
+
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
708
|
+
}
|
|
662
709
|
const result = await transport.send({
|
|
663
710
|
action: SDK_ACTIONS.makePayoutAndResolve,
|
|
664
711
|
payload
|
|
665
712
|
});
|
|
713
|
+
if (config.hooks?.afterPayout) {
|
|
714
|
+
await config.hooks.afterPayout(
|
|
715
|
+
result.isOk ? slangTs.Ok({
|
|
716
|
+
reference: result.value.reference,
|
|
717
|
+
status: result.value.status
|
|
718
|
+
}) : slangTs.Err(result.error),
|
|
719
|
+
payload
|
|
720
|
+
);
|
|
721
|
+
}
|
|
666
722
|
if (result.isOk) {
|
|
667
723
|
return slangTs.Ok(result.value);
|
|
668
724
|
}
|
|
@@ -747,6 +803,7 @@ function createSdkInstance(config) {
|
|
|
747
803
|
}
|
|
748
804
|
|
|
749
805
|
// src/create-nylon-pay.ts
|
|
806
|
+
var instances = /* @__PURE__ */ new Map();
|
|
750
807
|
function createNylonPay(config) {
|
|
751
808
|
if (!config.apiKey) {
|
|
752
809
|
throw new Error("apiKey is required");
|
|
@@ -760,18 +817,27 @@ function createNylonPay(config) {
|
|
|
760
817
|
if (!config.apiSecret.startsWith("nps_")) {
|
|
761
818
|
throw new Error('apiSecret must start with "nps_"');
|
|
762
819
|
}
|
|
820
|
+
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
821
|
+
const instanceKey = `${config.apiKey}:${baseUrl}`;
|
|
822
|
+
if (!config.force) {
|
|
823
|
+
const existing = instances.get(instanceKey);
|
|
824
|
+
if (existing) return existing;
|
|
825
|
+
}
|
|
763
826
|
const resolvedConfig = {
|
|
764
827
|
apiKey: config.apiKey,
|
|
765
828
|
apiSecret: config.apiSecret,
|
|
766
|
-
baseUrl
|
|
829
|
+
baseUrl,
|
|
767
830
|
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
768
831
|
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
769
832
|
maxPollIntervalMs: config.maxPollIntervalMs ?? DEFAULT_MAX_POLL_INTERVAL_MS,
|
|
770
833
|
maxPollDurationMs: config.maxPollDurationMs ?? DEFAULT_MAX_POLL_DURATION_MS,
|
|
771
834
|
maxPollAttempts: config.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS,
|
|
772
|
-
fetch: config.fetch ?? globalThis.fetch.bind(globalThis)
|
|
835
|
+
fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
|
|
836
|
+
hooks: config.hooks
|
|
773
837
|
};
|
|
774
|
-
|
|
838
|
+
const instance = createSdkInstance(resolvedConfig);
|
|
839
|
+
instances.set(instanceKey, instance);
|
|
840
|
+
return instance;
|
|
775
841
|
}
|
|
776
842
|
|
|
777
843
|
exports.createNylonPay = createNylonPay;
|