@tineon/t9n 0.1.4 → 0.1.5
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 +12 -6
- package/dist/index.d.ts +7 -1
- package/dist/index.js +117 -5
- package/dist/types.d.ts +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ npm i @tineon/t9n
|
|
|
26
26
|
## Quickstart
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
|
-
import { initializeT9n } from "@tineon/t9n";
|
|
29
|
+
import { initializeT9n, verifyT9nTransaction } from "@tineon/t9n";
|
|
30
30
|
|
|
31
31
|
const checkout = initializeT9n({
|
|
32
32
|
publicKey: "pk_live_xxx",
|
|
@@ -41,11 +41,17 @@ const checkout = initializeT9n({
|
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
checkout.mountButton("#pay-btn-slot", {
|
|
45
|
-
text: "Pay with Crypto",
|
|
46
|
-
theme: "solid",
|
|
47
|
-
});
|
|
48
|
-
|
|
44
|
+
checkout.mountButton("#pay-btn-slot", {
|
|
45
|
+
text: "Pay with Crypto",
|
|
46
|
+
theme: "solid",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const verification = await verifyT9nTransaction(
|
|
50
|
+
{ publicKey: "pk_live_xxx" },
|
|
51
|
+
{ reference: "order-123" }
|
|
52
|
+
);
|
|
53
|
+
console.log("verified", verification.status);
|
|
54
|
+
```
|
|
49
55
|
|
|
50
56
|
## Triggers
|
|
51
57
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CheckoutConfig, type T9nButtonOptions } from "./types.js";
|
|
1
|
+
import { type CheckoutConfig, type T9nButtonOptions, type VerifyTransactionParams, type VerifyTransactionResult } from "./types.js";
|
|
2
2
|
export declare class T9nCheckout {
|
|
3
3
|
private cfg;
|
|
4
4
|
private currencies;
|
|
@@ -10,6 +10,8 @@ export declare class T9nCheckout {
|
|
|
10
10
|
private isOpen;
|
|
11
11
|
private lastResultFailed;
|
|
12
12
|
private hasAttemptedConfirm;
|
|
13
|
+
private successNotified;
|
|
14
|
+
private failureNotified;
|
|
13
15
|
constructor(config: CheckoutConfig);
|
|
14
16
|
createButton(options?: T9nButtonOptions): HTMLButtonElement;
|
|
15
17
|
bindTrigger(target: string | HTMLElement, eventName?: keyof HTMLElementEventMap): () => void;
|
|
@@ -21,6 +23,7 @@ export declare class T9nCheckout {
|
|
|
21
23
|
private confirmPayment;
|
|
22
24
|
private startTimer;
|
|
23
25
|
private startStatusPolling;
|
|
26
|
+
verifyTransaction(params: VerifyTransactionParams): Promise<VerifyTransactionResult>;
|
|
24
27
|
private defaultNetworkFor;
|
|
25
28
|
private fetchWithTimeout;
|
|
26
29
|
private assertSecureConfig;
|
|
@@ -30,6 +33,9 @@ export declare class T9nCheckout {
|
|
|
30
33
|
private markFailed;
|
|
31
34
|
private markClosed;
|
|
32
35
|
private emitStatus;
|
|
36
|
+
private emitSuccessOnce;
|
|
37
|
+
private emitFailOnce;
|
|
33
38
|
private getApiBaseUrl;
|
|
34
39
|
}
|
|
35
40
|
export declare function initializeT9n(config: CheckoutConfig): T9nCheckout;
|
|
41
|
+
export declare function verifyT9nTransaction(config: Pick<CheckoutConfig, "publicKey" | "requestTimeoutMs">, params: VerifyTransactionParams): Promise<VerifyTransactionResult>;
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export class T9nCheckout {
|
|
|
8
8
|
this.isOpen = false;
|
|
9
9
|
this.lastResultFailed = false;
|
|
10
10
|
this.hasAttemptedConfirm = false;
|
|
11
|
+
this.successNotified = false;
|
|
12
|
+
this.failureNotified = false;
|
|
11
13
|
this.cfg = checkoutConfigSchema.parse(config);
|
|
12
14
|
this.cfg.apiBaseUrl = this.cfg.apiBaseUrl || resolveDefaultApiBaseUrl();
|
|
13
15
|
this.currencies = this.cfg.currencies;
|
|
@@ -47,6 +49,8 @@ export class T9nCheckout {
|
|
|
47
49
|
this.isOpen = true;
|
|
48
50
|
this.lastResultFailed = false;
|
|
49
51
|
this.hasAttemptedConfirm = false;
|
|
52
|
+
this.successNotified = false;
|
|
53
|
+
this.failureNotified = false;
|
|
50
54
|
try {
|
|
51
55
|
this.cfg.hooks?.onOpen?.();
|
|
52
56
|
const session = await this.createSession();
|
|
@@ -84,6 +88,8 @@ export class T9nCheckout {
|
|
|
84
88
|
this.sessionId = "";
|
|
85
89
|
this.expiresAt = undefined;
|
|
86
90
|
this.isOpen = false;
|
|
91
|
+
this.successNotified = false;
|
|
92
|
+
this.failureNotified = false;
|
|
87
93
|
this.emitStatus("closed");
|
|
88
94
|
this.cfg.hooks?.onClose?.();
|
|
89
95
|
}
|
|
@@ -186,7 +192,7 @@ export class T9nCheckout {
|
|
|
186
192
|
this.lastResultFailed = true;
|
|
187
193
|
this.modal?.setConfirmLabel("Retry check");
|
|
188
194
|
this.emitStatus("pending_confirmation");
|
|
189
|
-
this.
|
|
195
|
+
this.emitFailOnce({ sessionId: this.sessionId || undefined, error: "confirm request failed" });
|
|
190
196
|
throw new Error("T9N: failed to confirm payment");
|
|
191
197
|
}
|
|
192
198
|
const payload = (await res.json());
|
|
@@ -202,11 +208,12 @@ export class T9nCheckout {
|
|
|
202
208
|
if (payload.status === "settled") {
|
|
203
209
|
this.modal?.showResult("success", "Payment received successfully.");
|
|
204
210
|
this.lastResultFailed = false;
|
|
205
|
-
this.
|
|
211
|
+
this.emitSuccessOnce({ sessionId: this.sessionId, status: payload.status });
|
|
206
212
|
}
|
|
207
213
|
if (payload.status === "expired") {
|
|
208
214
|
this.modal?.showResult("failed", "This session has expired.");
|
|
209
215
|
this.lastResultFailed = true;
|
|
216
|
+
this.emitFailOnce({ sessionId: this.sessionId, error: "checkout expired" });
|
|
210
217
|
}
|
|
211
218
|
}
|
|
212
219
|
startTimer() {
|
|
@@ -251,13 +258,13 @@ export class T9nCheckout {
|
|
|
251
258
|
window.clearInterval(this.intervalId);
|
|
252
259
|
if (this.statusPollId)
|
|
253
260
|
window.clearInterval(this.statusPollId);
|
|
254
|
-
this.
|
|
261
|
+
this.emitSuccessOnce({ sessionId: this.sessionId, status: payload.status || "settled" });
|
|
255
262
|
}
|
|
256
263
|
if (normalized === "failed") {
|
|
257
264
|
if (this.hasAttemptedConfirm) {
|
|
258
265
|
this.modal?.showResult("failed", "No payment detected yet. Please try again.");
|
|
259
266
|
this.lastResultFailed = true;
|
|
260
|
-
this.
|
|
267
|
+
this.emitFailOnce({ sessionId: this.sessionId, error: "checkout failed" });
|
|
261
268
|
}
|
|
262
269
|
}
|
|
263
270
|
}
|
|
@@ -268,14 +275,52 @@ export class T9nCheckout {
|
|
|
268
275
|
window.clearInterval(this.intervalId);
|
|
269
276
|
if (this.statusPollId)
|
|
270
277
|
window.clearInterval(this.statusPollId);
|
|
278
|
+
this.emitFailOnce({ sessionId: this.sessionId, error: "checkout expired" });
|
|
271
279
|
}
|
|
272
280
|
}
|
|
273
281
|
catch (_) {
|
|
274
282
|
// keep modal alive even if one poll fails
|
|
275
|
-
this.cfg.hooks?.onFail?.({ sessionId: this.sessionId || undefined, error: "session status poll failed" });
|
|
276
283
|
}
|
|
277
284
|
}, this.cfg.pollIntervalMs);
|
|
278
285
|
}
|
|
286
|
+
async verifyTransaction(params) {
|
|
287
|
+
const sessionId = params?.sessionId?.trim();
|
|
288
|
+
const reference = params?.reference?.trim();
|
|
289
|
+
if (!sessionId && !reference) {
|
|
290
|
+
throw new Error("T9N: sessionId or reference is required for verification");
|
|
291
|
+
}
|
|
292
|
+
const search = new URLSearchParams();
|
|
293
|
+
if (sessionId)
|
|
294
|
+
search.set("sessionId", sessionId);
|
|
295
|
+
if (reference)
|
|
296
|
+
search.set("reference", reference);
|
|
297
|
+
const res = await this.fetchWithTimeout(`${this.getApiBaseUrl()}/api/merchant/checkout/verify?${search.toString()}`, {
|
|
298
|
+
method: "GET",
|
|
299
|
+
headers: {
|
|
300
|
+
"x-public-key": this.cfg.publicKey,
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
if (!res.ok) {
|
|
304
|
+
let message = "T9N: failed to verify transaction";
|
|
305
|
+
try {
|
|
306
|
+
const data = await res.json();
|
|
307
|
+
if (data?.message)
|
|
308
|
+
message = data.message;
|
|
309
|
+
}
|
|
310
|
+
catch (_) {
|
|
311
|
+
try {
|
|
312
|
+
const text = await res.text();
|
|
313
|
+
if (text)
|
|
314
|
+
message = text;
|
|
315
|
+
}
|
|
316
|
+
catch (_) {
|
|
317
|
+
// ignore
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
throw new Error(`T9N_VERIFY_ERROR_${res.status}: ${message}`);
|
|
321
|
+
}
|
|
322
|
+
return (await res.json());
|
|
323
|
+
}
|
|
279
324
|
defaultNetworkFor(currency) {
|
|
280
325
|
const map = {
|
|
281
326
|
BTC: "bitcoin",
|
|
@@ -391,6 +436,18 @@ export class T9nCheckout {
|
|
|
391
436
|
emitStatus(status) {
|
|
392
437
|
this.cfg.hooks?.onStatusChange?.(status);
|
|
393
438
|
}
|
|
439
|
+
emitSuccessOnce(payload) {
|
|
440
|
+
if (this.successNotified)
|
|
441
|
+
return;
|
|
442
|
+
this.successNotified = true;
|
|
443
|
+
this.cfg.hooks?.onSuccess?.(payload);
|
|
444
|
+
}
|
|
445
|
+
emitFailOnce(payload) {
|
|
446
|
+
if (this.successNotified || this.failureNotified)
|
|
447
|
+
return;
|
|
448
|
+
this.failureNotified = true;
|
|
449
|
+
this.cfg.hooks?.onFail?.(payload);
|
|
450
|
+
}
|
|
394
451
|
getApiBaseUrl() {
|
|
395
452
|
return this.cfg.apiBaseUrl || resolveDefaultApiBaseUrl();
|
|
396
453
|
}
|
|
@@ -401,3 +458,58 @@ function resolveDefaultApiBaseUrl() {
|
|
|
401
458
|
export function initializeT9n(config) {
|
|
402
459
|
return new T9nCheckout(config);
|
|
403
460
|
}
|
|
461
|
+
export async function verifyT9nTransaction(config, params) {
|
|
462
|
+
const publicKey = config.publicKey?.trim();
|
|
463
|
+
if (!publicKey || !/^pk_(live|test)_[a-zA-Z0-9_]+$/.test(publicKey)) {
|
|
464
|
+
throw new Error("T9N: publicKey must follow pk_live_* or pk_test_* format");
|
|
465
|
+
}
|
|
466
|
+
const sessionId = params?.sessionId?.trim();
|
|
467
|
+
const reference = params?.reference?.trim();
|
|
468
|
+
if (!sessionId && !reference) {
|
|
469
|
+
throw new Error("T9N: sessionId or reference is required for verification");
|
|
470
|
+
}
|
|
471
|
+
const apiBaseUrl = resolveDefaultApiBaseUrl().trim();
|
|
472
|
+
const parsedURL = new URL(apiBaseUrl);
|
|
473
|
+
if (parsedURL.protocol !== "https:" && parsedURL.hostname !== "localhost" && parsedURL.hostname !== "127.0.0.1") {
|
|
474
|
+
throw new Error("T9N: apiBaseUrl must use HTTPS");
|
|
475
|
+
}
|
|
476
|
+
const search = new URLSearchParams();
|
|
477
|
+
if (sessionId)
|
|
478
|
+
search.set("sessionId", sessionId);
|
|
479
|
+
if (reference)
|
|
480
|
+
search.set("reference", reference);
|
|
481
|
+
const controller = new AbortController();
|
|
482
|
+
const timeout = window.setTimeout(() => controller.abort(), config.requestTimeoutMs || 12000);
|
|
483
|
+
try {
|
|
484
|
+
const res = await fetch(`${apiBaseUrl}/api/merchant/checkout/verify?${search.toString()}`, {
|
|
485
|
+
method: "GET",
|
|
486
|
+
headers: {
|
|
487
|
+
"x-public-key": publicKey,
|
|
488
|
+
},
|
|
489
|
+
signal: controller.signal,
|
|
490
|
+
});
|
|
491
|
+
if (!res.ok) {
|
|
492
|
+
let message = "T9N: failed to verify transaction";
|
|
493
|
+
try {
|
|
494
|
+
const data = await res.json();
|
|
495
|
+
if (data?.message)
|
|
496
|
+
message = data.message;
|
|
497
|
+
}
|
|
498
|
+
catch (_) {
|
|
499
|
+
try {
|
|
500
|
+
const text = await res.text();
|
|
501
|
+
if (text)
|
|
502
|
+
message = text;
|
|
503
|
+
}
|
|
504
|
+
catch (_) {
|
|
505
|
+
// ignore
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
throw new Error(`T9N_VERIFY_ERROR_${res.status}: ${message}`);
|
|
509
|
+
}
|
|
510
|
+
return (await res.json());
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
window.clearTimeout(timeout);
|
|
514
|
+
}
|
|
515
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -33,6 +33,23 @@ export type T9nHooks = {
|
|
|
33
33
|
error: string;
|
|
34
34
|
}) => void;
|
|
35
35
|
};
|
|
36
|
+
export type VerifyTransactionParams = {
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
reference?: string;
|
|
39
|
+
};
|
|
40
|
+
export type VerifyTransactionResult = {
|
|
41
|
+
sessionId: string;
|
|
42
|
+
reference?: string;
|
|
43
|
+
status: string;
|
|
44
|
+
selectedCurrency?: string;
|
|
45
|
+
selectedNetwork?: string;
|
|
46
|
+
depositAddress?: string;
|
|
47
|
+
expectedAmountCrypto?: string;
|
|
48
|
+
expiresAt?: string;
|
|
49
|
+
createdAt?: string;
|
|
50
|
+
updatedAt?: string;
|
|
51
|
+
settledAt?: string;
|
|
52
|
+
};
|
|
36
53
|
export declare const T9N_DEFAULT_API_BASE_URL = "https://slimepay-server.up.railway.app";
|
|
37
54
|
export declare const checkoutConfigSchema: z.ZodObject<{
|
|
38
55
|
publicKey: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, string, string>;
|