@tineon/t9n 0.1.4 → 0.1.6
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 +129 -7
- 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,13 +192,17 @@ 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());
|
|
193
199
|
this.modal?.setConfirmPending(false);
|
|
194
|
-
this.
|
|
195
|
-
|
|
200
|
+
const normalized = this.normalizeStatus(payload.status);
|
|
201
|
+
this.emitStatus(normalized);
|
|
202
|
+
if (payload.status === "pending" ||
|
|
203
|
+
payload.status === "processing" ||
|
|
204
|
+
normalized === "pending_confirmation" ||
|
|
205
|
+
normalized === "awaiting_payment") {
|
|
196
206
|
this.modal?.showResult("failed", "No payment detected yet. Please try again.");
|
|
197
207
|
this.lastResultFailed = true;
|
|
198
208
|
this.modal?.setConfirmLabel("Retry check");
|
|
@@ -202,11 +212,12 @@ export class T9nCheckout {
|
|
|
202
212
|
if (payload.status === "settled") {
|
|
203
213
|
this.modal?.showResult("success", "Payment received successfully.");
|
|
204
214
|
this.lastResultFailed = false;
|
|
205
|
-
this.
|
|
215
|
+
this.emitSuccessOnce({ sessionId: this.sessionId, status: payload.status });
|
|
206
216
|
}
|
|
207
217
|
if (payload.status === "expired") {
|
|
208
218
|
this.modal?.showResult("failed", "This session has expired.");
|
|
209
219
|
this.lastResultFailed = true;
|
|
220
|
+
this.emitFailOnce({ sessionId: this.sessionId, error: "checkout expired" });
|
|
210
221
|
}
|
|
211
222
|
}
|
|
212
223
|
startTimer() {
|
|
@@ -251,13 +262,19 @@ export class T9nCheckout {
|
|
|
251
262
|
window.clearInterval(this.intervalId);
|
|
252
263
|
if (this.statusPollId)
|
|
253
264
|
window.clearInterval(this.statusPollId);
|
|
254
|
-
this.
|
|
265
|
+
this.emitSuccessOnce({ sessionId: this.sessionId, status: payload.status || "settled" });
|
|
255
266
|
}
|
|
256
267
|
if (normalized === "failed") {
|
|
257
268
|
if (this.hasAttemptedConfirm) {
|
|
258
269
|
this.modal?.showResult("failed", "No payment detected yet. Please try again.");
|
|
259
270
|
this.lastResultFailed = true;
|
|
260
|
-
this.
|
|
271
|
+
this.emitFailOnce({ sessionId: this.sessionId, error: "checkout failed" });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (normalized === "pending_confirmation" || normalized === "awaiting_payment") {
|
|
275
|
+
if (this.hasAttemptedConfirm) {
|
|
276
|
+
this.modal?.showResult("failed", "No payment detected yet. Please try again.");
|
|
277
|
+
this.lastResultFailed = true;
|
|
261
278
|
}
|
|
262
279
|
}
|
|
263
280
|
}
|
|
@@ -268,14 +285,52 @@ export class T9nCheckout {
|
|
|
268
285
|
window.clearInterval(this.intervalId);
|
|
269
286
|
if (this.statusPollId)
|
|
270
287
|
window.clearInterval(this.statusPollId);
|
|
288
|
+
this.emitFailOnce({ sessionId: this.sessionId, error: "checkout expired" });
|
|
271
289
|
}
|
|
272
290
|
}
|
|
273
291
|
catch (_) {
|
|
274
292
|
// keep modal alive even if one poll fails
|
|
275
|
-
this.cfg.hooks?.onFail?.({ sessionId: this.sessionId || undefined, error: "session status poll failed" });
|
|
276
293
|
}
|
|
277
294
|
}, this.cfg.pollIntervalMs);
|
|
278
295
|
}
|
|
296
|
+
async verifyTransaction(params) {
|
|
297
|
+
const sessionId = params?.sessionId?.trim();
|
|
298
|
+
const reference = params?.reference?.trim();
|
|
299
|
+
if (!sessionId && !reference) {
|
|
300
|
+
throw new Error("T9N: sessionId or reference is required for verification");
|
|
301
|
+
}
|
|
302
|
+
const search = new URLSearchParams();
|
|
303
|
+
if (sessionId)
|
|
304
|
+
search.set("sessionId", sessionId);
|
|
305
|
+
if (reference)
|
|
306
|
+
search.set("reference", reference);
|
|
307
|
+
const res = await this.fetchWithTimeout(`${this.getApiBaseUrl()}/api/merchant/checkout/verify?${search.toString()}`, {
|
|
308
|
+
method: "GET",
|
|
309
|
+
headers: {
|
|
310
|
+
"x-public-key": this.cfg.publicKey,
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
if (!res.ok) {
|
|
314
|
+
let message = "T9N: failed to verify transaction";
|
|
315
|
+
try {
|
|
316
|
+
const data = await res.json();
|
|
317
|
+
if (data?.message)
|
|
318
|
+
message = data.message;
|
|
319
|
+
}
|
|
320
|
+
catch (_) {
|
|
321
|
+
try {
|
|
322
|
+
const text = await res.text();
|
|
323
|
+
if (text)
|
|
324
|
+
message = text;
|
|
325
|
+
}
|
|
326
|
+
catch (_) {
|
|
327
|
+
// ignore
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
throw new Error(`T9N_VERIFY_ERROR_${res.status}: ${message}`);
|
|
331
|
+
}
|
|
332
|
+
return (await res.json());
|
|
333
|
+
}
|
|
279
334
|
defaultNetworkFor(currency) {
|
|
280
335
|
const map = {
|
|
281
336
|
BTC: "bitcoin",
|
|
@@ -391,6 +446,18 @@ export class T9nCheckout {
|
|
|
391
446
|
emitStatus(status) {
|
|
392
447
|
this.cfg.hooks?.onStatusChange?.(status);
|
|
393
448
|
}
|
|
449
|
+
emitSuccessOnce(payload) {
|
|
450
|
+
if (this.successNotified)
|
|
451
|
+
return;
|
|
452
|
+
this.successNotified = true;
|
|
453
|
+
this.cfg.hooks?.onSuccess?.(payload);
|
|
454
|
+
}
|
|
455
|
+
emitFailOnce(payload) {
|
|
456
|
+
if (this.successNotified || this.failureNotified)
|
|
457
|
+
return;
|
|
458
|
+
this.failureNotified = true;
|
|
459
|
+
this.cfg.hooks?.onFail?.(payload);
|
|
460
|
+
}
|
|
394
461
|
getApiBaseUrl() {
|
|
395
462
|
return this.cfg.apiBaseUrl || resolveDefaultApiBaseUrl();
|
|
396
463
|
}
|
|
@@ -401,3 +468,58 @@ function resolveDefaultApiBaseUrl() {
|
|
|
401
468
|
export function initializeT9n(config) {
|
|
402
469
|
return new T9nCheckout(config);
|
|
403
470
|
}
|
|
471
|
+
export async function verifyT9nTransaction(config, params) {
|
|
472
|
+
const publicKey = config.publicKey?.trim();
|
|
473
|
+
if (!publicKey || !/^pk_(live|test)_[a-zA-Z0-9_]+$/.test(publicKey)) {
|
|
474
|
+
throw new Error("T9N: publicKey must follow pk_live_* or pk_test_* format");
|
|
475
|
+
}
|
|
476
|
+
const sessionId = params?.sessionId?.trim();
|
|
477
|
+
const reference = params?.reference?.trim();
|
|
478
|
+
if (!sessionId && !reference) {
|
|
479
|
+
throw new Error("T9N: sessionId or reference is required for verification");
|
|
480
|
+
}
|
|
481
|
+
const apiBaseUrl = resolveDefaultApiBaseUrl().trim();
|
|
482
|
+
const parsedURL = new URL(apiBaseUrl);
|
|
483
|
+
if (parsedURL.protocol !== "https:" && parsedURL.hostname !== "localhost" && parsedURL.hostname !== "127.0.0.1") {
|
|
484
|
+
throw new Error("T9N: apiBaseUrl must use HTTPS");
|
|
485
|
+
}
|
|
486
|
+
const search = new URLSearchParams();
|
|
487
|
+
if (sessionId)
|
|
488
|
+
search.set("sessionId", sessionId);
|
|
489
|
+
if (reference)
|
|
490
|
+
search.set("reference", reference);
|
|
491
|
+
const controller = new AbortController();
|
|
492
|
+
const timeout = window.setTimeout(() => controller.abort(), config.requestTimeoutMs || 12000);
|
|
493
|
+
try {
|
|
494
|
+
const res = await fetch(`${apiBaseUrl}/api/merchant/checkout/verify?${search.toString()}`, {
|
|
495
|
+
method: "GET",
|
|
496
|
+
headers: {
|
|
497
|
+
"x-public-key": publicKey,
|
|
498
|
+
},
|
|
499
|
+
signal: controller.signal,
|
|
500
|
+
});
|
|
501
|
+
if (!res.ok) {
|
|
502
|
+
let message = "T9N: failed to verify transaction";
|
|
503
|
+
try {
|
|
504
|
+
const data = await res.json();
|
|
505
|
+
if (data?.message)
|
|
506
|
+
message = data.message;
|
|
507
|
+
}
|
|
508
|
+
catch (_) {
|
|
509
|
+
try {
|
|
510
|
+
const text = await res.text();
|
|
511
|
+
if (text)
|
|
512
|
+
message = text;
|
|
513
|
+
}
|
|
514
|
+
catch (_) {
|
|
515
|
+
// ignore
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
throw new Error(`T9N_VERIFY_ERROR_${res.status}: ${message}`);
|
|
519
|
+
}
|
|
520
|
+
return (await res.json());
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
window.clearTimeout(timeout);
|
|
524
|
+
}
|
|
525
|
+
}
|
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>;
|