suioutkit 1.0.1 → 1.0.3

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.
@@ -55,3 +55,14 @@ export interface CheckoutStatusResponse {
55
55
  walrusBlobId?: string;
56
56
  error?: string;
57
57
  }
58
+ export interface PaymentResult {
59
+ nonce: string;
60
+ txDigest: string;
61
+ walrusBlobId: string;
62
+ }
63
+ export interface SuiOutKitModalOptions {
64
+ onClose?: () => void;
65
+ onPaymentComplete?: (result: PaymentResult) => void;
66
+ redirectUrl?: string;
67
+ autoCloseOnSuccess?: boolean;
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suioutkit",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Premium Universal Payment Gateway SDK for instant settlement on Sui",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,10 +25,12 @@
25
25
  "@mysten/sui": "^2.17.0",
26
26
  "@stripe/stripe-js": "^9.7.0",
27
27
  "lucide-react": "^1.17.0",
28
+ "qrcode": "^1.5.4",
28
29
  "react": "^19.2.7",
29
30
  "react-dom": "^19.2.7"
30
31
  },
31
32
  "devDependencies": {
33
+ "@types/qrcode": "^1.5.6",
32
34
  "@types/react": "^19.2.16",
33
35
  "@types/react-dom": "^19.2.3",
34
36
  "esbuild": "^0.28.0",
@@ -10,8 +10,9 @@ import { createPaymentTransactionUri } from "@mysten/payment-kit";
10
10
  import { paymentKit } from "@mysten/payment-kit";
11
11
  import { createDAppKit } from "@mysten/dapp-kit-core";
12
12
  import "@mysten/dapp-kit-core/web";
13
+ import QRCode from "qrcode";
13
14
  import { loadStripe, StripeElements, Stripe } from "@stripe/stripe-js";
14
- import { CheckoutSession, ChargeResponse, CheckoutStatusResponse, CryptoIntentResponse } from "../types/index.js";
15
+ import { CheckoutSession, ChargeResponse, CheckoutStatusResponse, CryptoIntentResponse, SuiOutKitModalOptions, PaymentResult } from "../types/index.js";
15
16
  import PaymentStatusUI from "./PaymentStatusUI";
16
17
  import { joinApiPath } from "../config/api.js";
17
18
 
@@ -33,17 +34,23 @@ export class SuiOutKitModal {
33
34
  private backendUrl: string;
34
35
  private pollInterval: any = null;
35
36
  private walletConnectionUnsubscribe: (() => void) | null = null;
36
- private onCloseCallback: () => void;
37
+ private onCloseCallback?: () => void;
38
+ private onPaymentCompleteCallback?: (result: PaymentResult) => void;
39
+ private redirectUrl?: string;
40
+ private autoCloseOnSuccess?: boolean;
37
41
  private cryptoIntent: CryptoIntentResponse | null = null;
38
42
  private dAppKit: any | null = null;
39
43
  private paymentClient: any | null = null;
40
44
  private stripeInstance: Stripe | null = null;
41
45
  private stripeElements: StripeElements | null = null;
42
46
 
43
- constructor(session: CheckoutSession, backendUrl: string, onClose: () => void) {
47
+ constructor(session: CheckoutSession, backendUrl: string, options?: SuiOutKitModalOptions) {
44
48
  this.session = session;
45
49
  this.backendUrl = backendUrl;
46
- this.onCloseCallback = onClose;
50
+ this.onCloseCallback = options?.onClose;
51
+ this.onPaymentCompleteCallback = options?.onPaymentComplete;
52
+ this.redirectUrl = options?.redirectUrl;
53
+ this.autoCloseOnSuccess = options?.autoCloseOnSuccess;
47
54
  this.ensureDAppKit(); // Initialize early so wallets have time to inject
48
55
  this.injectStyles();
49
56
  this.createModal();
@@ -271,8 +278,8 @@ export class SuiOutKitModal {
271
278
  <h2 class="suioutkit-title">OPay Direct</h2>
272
279
  <p class="suioutkit-subtitle">Enter your OPay registered phone number</p>
273
280
  </div>
274
- <div class="sok-panel">
275
- <form class="sok-form" id="sok-opay-form">
281
+ <div class="suioutkit-panel">
282
+ <form class="sok-form" id="sok-opay-form" style="width: 100%;">
276
283
  <input type="tel" class="sok-input" placeholder="e.g. 08012345678" id="sok-phone-input" required />
277
284
  <button type="submit" class="sok-btn">Send Prompt</button>
278
285
  </form>
@@ -348,12 +355,12 @@ export class SuiOutKitModal {
348
355
  <h2 class="suioutkit-title">Global Checkout</h2>
349
356
  <p class="suioutkit-subtitle">Secured by Stripe</p>
350
357
  </div>
351
- <div class="suioutkit-panel" style="gap: 16px; display: flex; flex-direction: column; width: 100%;">
358
+ <div class="suioutkit-panel">
352
359
  <form id="payment-form" style="width: 100%;">
353
360
  <div id="payment-element" style="min-height: 200px; margin-bottom: 16px;">
354
361
  <div class="sok-spinner" style="margin: 0 auto;"></div>
355
362
  </div>
356
- <button class="sok-btn" id="submit-stripe-btn" style="background: linear-gradient(135deg, #6366f1 0%, #4338ca 100%); width: 100%;">
363
+ <button class="sok-btn sok-btn-indigo" id="submit-stripe-btn">
357
364
  Pay Now
358
365
  </button>
359
366
  <div id="payment-message" style="color: #ef4444; font-size: 13px; margin-top: 8px; text-align: center; display: none;"></div>
@@ -413,29 +420,20 @@ export class SuiOutKitModal {
413
420
  const container = this.overlay?.querySelector("#sok-content-panel");
414
421
  if (!container) return;
415
422
 
416
- this.renderLoadingPanel("Preparing crypto payment...");
417
-
418
- try {
419
- this.cryptoIntent = await this.loadCryptoIntent("sui_wallet");
420
- } catch (err: any) {
421
- this.renderErrorPanel(err.message || "Failed to prepare crypto payment.");
422
- return;
423
- }
424
-
425
423
  container.innerHTML = `
426
424
  <button class="suioutkit-back" id="sok-back-btn">← Back to methods</button>
427
425
  <div class="suioutkit-header">
428
426
  <h2 class="suioutkit-title">Pay with Sui Wallet</h2>
429
427
  <p class="suioutkit-subtitle">Choose SUI payment channel</p>
430
428
  </div>
431
- <div class="suioutkit-panel" style="gap: 12px; display: flex; flex-direction: column; width: 100%;">
429
+ <div class="suioutkit-panel">
432
430
  <p class="sok-status-text" style="margin-bottom: 12px;">
433
431
  Choose whether to pay via a desktop extension wallet or scan a dynamic QR Code with your mobile wallet.
434
432
  </p>
435
- <button class="sok-btn" id="sok-connect-extension-btn" style="background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); margin-bottom: 4px;">
433
+ <button class="sok-btn sok-btn-blue" id="sok-connect-extension-btn" style="margin-bottom: 4px;">
436
434
  Standard Connect Wallet
437
435
  </button>
438
- <button class="sok-btn" id="sok-outpay-qr-btn" style="background: linear-gradient(135deg, #10b981 0%, #047857 100%);">
436
+ <button class="sok-btn sok-btn-green" id="sok-outpay-qr-btn">
439
437
  outPay (Scan QR Code)
440
438
  </button>
441
439
  </div>
@@ -443,9 +441,12 @@ export class SuiOutKitModal {
443
441
 
444
442
  container.querySelector("#sok-back-btn")?.addEventListener("click", () => this.renderSelectionPanel());
445
443
 
446
- container.querySelector("#sok-connect-extension-btn")?.addEventListener("click", () => {
447
- if (!this.cryptoIntent) {
448
- this.renderErrorPanel("Crypto intent not ready.");
444
+ container.querySelector("#sok-connect-extension-btn")?.addEventListener("click", async () => {
445
+ this.renderLoadingPanel("Preparing crypto payment...");
446
+ try {
447
+ this.cryptoIntent = await this.loadCryptoIntent("sui_wallet");
448
+ } catch (err: any) {
449
+ this.renderErrorPanel(err.message || "Failed to prepare crypto payment.");
449
450
  return;
450
451
  }
451
452
  void this.openStandardConnectWallet();
@@ -517,10 +518,10 @@ export class SuiOutKitModal {
517
518
  <h2 class="suioutkit-title">Connect Wallet</h2>
518
519
  <p class="suioutkit-subtitle">Choose the extension you want to use</p>
519
520
  </div>
520
- <div style="display: grid; grid-template-columns: 1fr; gap: 12px; width: 100%;">
521
+ <div class="suioutkit-wallet-list">
521
522
  ${walletCardsHtml}
522
523
  </div>
523
- <p class="sok-status-text" style="font-size: 12px; opacity: 0.75; margin-top: 14px; text-align: center;">
524
+ <p class="sok-status-text sok-text-sm sok-op-75" style="margin-top: 14px; text-align: center;">
524
525
  Wallets are filtered from the browser extensions detected by dApp Kit.
525
526
  </p>
526
527
  `;
@@ -598,15 +599,15 @@ export class SuiOutKitModal {
598
599
 
599
600
  container.innerHTML = `
600
601
  <button class="suioutkit-back" id="sok-back-btn">← Back to Sui options</button>
601
- <div class="suioutkit-panel" style="gap: 12px; display: flex; flex-direction: column; align-items: center; text-align: center;">
602
- <div class="sok-success-icon" style="color: #f59e0b; display: flex; align-items: center; justify-content: center; margin-bottom: 8px;">
602
+ <div class="suioutkit-panel">
603
+ <div class="sok-icon-wrap sok-text-amber">
603
604
  <i data-lucide="alert-circle" style="width: 48px; height: 48px;"></i>
604
605
  </div>
605
606
  <h2 class="sok-success-title">Open this demo from localhost</h2>
606
607
  <p class="sok-status-text" style="max-width: 320px;">
607
608
  This page is running from a local file URL. Browser extension wallets like Slush and Phantom do not reliably inject into file:// pages, so dApp Kit cannot list them here.
608
609
  </p>
609
- <p class="sok-status-text" style="max-width: 320px; font-size: 12px; opacity: 0.78;">
610
+ <p class="sok-status-text sok-text-sm sok-op-75" style="max-width: 320px;">
610
611
  Open the demo over http://localhost or another web server, then reload. That is the supported origin for wallet detection and connection.
611
612
  </p>
612
613
  </div>
@@ -624,14 +625,13 @@ export class SuiOutKitModal {
624
625
  <button
625
626
  class="sok-wallet-card"
626
627
  data-wallet-index="${index}"
627
- style="display: flex; align-items: center; gap: 14px; width: 100%; padding: 14px 16px; border-radius: 18px; border: 1px solid rgba(255,255,255,0.08); background: linear-gradient(135deg, rgba(17,24,39,0.88), rgba(15,23,42,0.96)); color: #fff; text-align: left; box-shadow: 0 18px 40px rgba(0,0,0,0.22);"
628
628
  >
629
- <img src="${icon}" alt="${walletName}" class="sok-wallet-icon" style="width: 44px; height: 44px; border-radius: 14px; flex: none; background: rgba(255,255,255,0.08); padding: 4px;" />
630
- <span style="display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0;">
631
- <span class="sok-wallet-name" style="font-weight: 700; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${walletName}</span>
632
- <span style="font-size: 12px; opacity: 0.74;">Detected browser wallet</span>
629
+ <img src="${icon}" alt="${walletName}" class="sok-wallet-icon" />
630
+ <span class="sok-wallet-info">
631
+ <span class="sok-wallet-name">${walletName}</span>
632
+ <span class="sok-wallet-desc">Detected browser wallet</span>
633
633
  </span>
634
- <span style="font-size: 12px; font-weight: 700; color: #93c5fd;">Connect</span>
634
+ <span class="sok-wallet-connect">Connect</span>
635
635
  </button>
636
636
  `;
637
637
  }
@@ -643,14 +643,14 @@ export class SuiOutKitModal {
643
643
  container.innerHTML = `
644
644
  <button class="suioutkit-back" id="sok-back-btn">← Back to Sui options</button>
645
645
  <div class="suioutkit-panel">
646
- <div class="sok-success-icon" style="color: #f59e0b; display: flex; align-items: center; justify-content: center; margin-bottom: 16px;">
646
+ <div class="sok-icon-wrap sok-text-amber">
647
647
  <i data-lucide="alert-circle" style="width: 48px; height: 48px;"></i>
648
648
  </div>
649
649
  <h2 class="sok-success-title">No Wallets Detected</h2>
650
- <p class="sok-status-text" style="margin-top: 16px;">
650
+ <p class="sok-status-text sok-mt-16">
651
651
  We couldn't find any installed Sui wallets. Please install a wallet extension like Phantom, Slush, or others from the app store and refresh the page.
652
652
  </p>
653
- <p class="sok-status-text" style="font-size: 12px; opacity: 0.7; margin-top: 12px;">
653
+ <p class="sok-status-text sok-text-sm sok-op-75 sok-mt-12">
654
654
  Alternatively, you can use the outPay QR option to pay from a mobile wallet.
655
655
  </p>
656
656
  </div>
@@ -676,7 +676,7 @@ export class SuiOutKitModal {
676
676
  <h2 class="suioutkit-title">Confirm Payment</h2>
677
677
  <p class="suioutkit-subtitle">Review and approve this transaction</p>
678
678
  </div>
679
- <div class="suioutkit-panel" style="gap: 12px; display: flex; flex-direction: column; width: 100%;">
679
+ <div class="suioutkit-panel">
680
680
  <div class="sok-va-card">
681
681
  <div class="sok-va-row">
682
682
  <div class="sok-va-lbl">Amount</div>
@@ -691,7 +691,7 @@ export class SuiOutKitModal {
691
691
  <div class="sok-va-val">${network}</div>
692
692
  </div>
693
693
  </div>
694
- <button class="sok-btn" id="sok-confirm-pay-btn" style="background: linear-gradient(135deg, #10b981 0%, #047857 100%);">
694
+ <button class="sok-btn sok-btn-green" id="sok-confirm-pay-btn">
695
695
  Confirm & Pay
696
696
  </button>
697
697
  </div>
@@ -809,7 +809,7 @@ export class SuiOutKitModal {
809
809
  }
810
810
 
811
811
  const paymentUri = this.buildPaymentUri(this.cryptoIntent);
812
- const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(paymentUri)}`;
812
+ const qrCodeUrl = await QRCode.toDataURL(paymentUri, { width: 180, margin: 1 });
813
813
 
814
814
  container.innerHTML = `
815
815
  <button class="suioutkit-back" id="sok-back-btn">← Back to Sui options</button>
@@ -823,7 +823,7 @@ export class SuiOutKitModal {
823
823
  <div class="sok-qr-frame">
824
824
  <img src="${qrCodeUrl}" alt="outPay QR Code" class="sok-qr-img" />
825
825
  <div class="sok-qr-logo-badge">
826
- <i data-lucide="droplet" style="width: 16px; height: 16px; color: white;"></i>
826
+ <img src="${this.backendUrl}/assets/slush.jpeg" alt="Slush" style="width: 35px; height: 35px; border-radius: 16px;" />
827
827
  </div>
828
828
  <div class="sok-qr-scan-pulse"></div>
829
829
  </div>
@@ -899,8 +899,9 @@ export class SuiOutKitModal {
899
899
  const walrusNetworkPath = getExplorerNetworkPath();
900
900
 
901
901
  container.innerHTML = `
902
+ <button class="suioutkit-back" id="sok-back-btn">← Back to methods</button>
902
903
  <div class="suioutkit-panel">
903
- <div class="sok-success-icon" style="color: #10b981; display: flex; align-items: center; justify-content: center; margin-bottom: 16px;">
904
+ <div class="sok-icon-wrap sok-text-green">
904
905
  <i data-lucide="check-circle" style="width: 48px; height: 48px;"></i>
905
906
  </div>
906
907
  <h2 class="sok-success-title">Payment Successful!</h2>
@@ -909,7 +910,7 @@ export class SuiOutKitModal {
909
910
  <div class="sok-success-details">
910
911
  <div class="sok-receipt-row">
911
912
  <span class="sok-receipt-lbl">Amount Paid</span>
912
- <span class="sok-receipt-val" style="color: #10b981; font-weight:700;">
913
+ <span class="sok-receipt-val sok-text-green" style="font-weight:700;">
913
914
  ${this.session.currency === "NGN" ? "₦" : ""}${this.session.amount.toLocaleString()}
914
915
  </span>
915
916
  </div>
@@ -932,6 +933,15 @@ export class SuiOutKitModal {
932
933
  `;
933
934
 
934
935
  this.renderIcons();
936
+ container.querySelector("#sok-back-btn")?.addEventListener("click", () => this.renderSelectionPanel());
937
+
938
+ const result: PaymentResult = { nonce: this.session.nonce, txDigest, walrusBlobId };
939
+ this.onPaymentCompleteCallback?.(result);
940
+ if (this.redirectUrl) {
941
+ window.location.href = this.redirectUrl;
942
+ } else if (this.autoCloseOnSuccess) {
943
+ this.destroy();
944
+ }
935
945
  }
936
946
 
937
947
  private renderErrorPanel(message: string) {
@@ -941,11 +951,11 @@ export class SuiOutKitModal {
941
951
  container.innerHTML = `
942
952
  <button class="suioutkit-back" id="sok-back-btn">← Back to methods</button>
943
953
  <div class="suioutkit-panel">
944
- <div class="sok-success-icon" style="color: #ef4444; display: flex; align-items: center; justify-content: center; margin-bottom: 16px;">
954
+ <div class="sok-icon-wrap sok-text-red">
945
955
  <i data-lucide="x-circle" style="width: 48px; height: 48px;"></i>
946
956
  </div>
947
957
  <h2 class="sok-success-title">Payment Failed</h2>
948
- <p class="sok-status-text" style="color: #ef4444; margin-bottom: 20px;">${message}</p>
958
+ <p class="sok-status-text sok-mb-20 sok-text-red">${message}</p>
949
959
  </div>
950
960
  `;
951
961
 
@@ -986,7 +996,7 @@ export class SuiOutKitModal {
986
996
  this.overlay?.classList.remove("active");
987
997
  setTimeout(() => {
988
998
  this.overlay?.remove();
989
- this.onCloseCallback();
999
+ this.onCloseCallback?.();
990
1000
  }, 300);
991
1001
  }
992
1002
  }