@kwespay/widget 1.0.5 → 1.0.7

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.
@@ -103,6 +103,7 @@
103
103
  decimals: 18,
104
104
  coingeckoId: "ethereum",
105
105
  binanceSymbol: "ETHUSDT",
106
+
106
107
  },
107
108
  {
108
109
  symbol: "USDT",
@@ -16952,6 +16953,84 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
16952
16953
  }
16953
16954
  };
16954
16955
 
16956
+ function isMobileDevice() {
16957
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
16958
+ navigator.userAgent
16959
+ );
16960
+ }
16961
+
16962
+ function dispatchWidgetEvent(eventName, detail = {}) {
16963
+ const event = new CustomEvent(`kwespay:${eventName}`, {
16964
+ detail,
16965
+ bubbles: true,
16966
+ cancelable: true,
16967
+ });
16968
+ window.dispatchEvent(event);
16969
+ }
16970
+
16971
+ function truncateHash(hash, startChars = 10, endChars = 8) {
16972
+ if (!hash) return "";
16973
+ return `${hash.slice(0, startChars)}...${hash.slice(-endChars)}`;
16974
+ }
16975
+
16976
+ function getErrorType(error) {
16977
+ const msg = error?.message ?? "";
16978
+ if (
16979
+ error?.code === 4001 ||
16980
+ error?.code === "ACTION_REJECTED" ||
16981
+ msg.includes("rejected") ||
16982
+ msg.includes("denied") ||
16983
+ msg.includes("cancelled")
16984
+ )
16985
+ return "USER_REJECTED";
16986
+ if (msg.toLowerCase().includes("insufficient")) return "INSUFFICIENT_BALANCE";
16987
+ if (
16988
+ msg.includes("User rejected") ||
16989
+ msg.includes("User closed modal") ||
16990
+ msg.includes("Connection request reset")
16991
+ )
16992
+ return "CONNECTION_REJECTED";
16993
+ if (msg.includes("timeout")) return "TIMEOUT";
16994
+ return "UNKNOWN";
16995
+ }
16996
+
16997
+ function getErrorMessage(error, context = {}) {
16998
+ const type = getErrorType(error);
16999
+
17000
+ console.error("[KwesPay] Payment error breakdown:", {
17001
+ errorType: type,
17002
+ message: error?.message,
17003
+ code: error?.code,
17004
+ data: error?.data,
17005
+ reason: error?.reason,
17006
+ stack: error?.stack,
17007
+ raw: error,
17008
+ context,
17009
+ });
17010
+
17011
+ if (error?.data) console.error("[KwesPay] Contract revert data:", error.data);
17012
+ if (error?.transaction)
17013
+ console.error("[KwesPay] Failed tx details:", error.transaction);
17014
+ if (error?.receipt) console.error("[KwesPay] Tx receipt:", error.receipt);
17015
+
17016
+ switch (type) {
17017
+ case "USER_REJECTED":
17018
+ return "You cancelled the transaction in your wallet.";
17019
+ case "INSUFFICIENT_BALANCE":
17020
+ return `Not enough ${context.token || "funds"} to complete this payment.`;
17021
+ case "CONNECTION_REJECTED":
17022
+ return "Wallet connection was cancelled.";
17023
+ case "TIMEOUT":
17024
+ return "Connection timed out. Please try again.";
17025
+ default:
17026
+ return (
17027
+ error?.reason ||
17028
+ error?.message ||
17029
+ "Something went wrong while processing your payment."
17030
+ );
17031
+ }
17032
+ }
17033
+
16955
17034
  const WIDGET_STYLES = `
16956
17035
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
16957
17036
 
@@ -18141,180 +18220,12 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18141
18220
  `;
18142
18221
  }
18143
18222
 
18144
- function dispatchWidgetEvent(eventName, detail = {}) {
18145
- const event = new CustomEvent(`kwespay:${eventName}`, {
18146
- detail,
18147
- bubbles: true,
18148
- cancelable: true,
18149
- });
18150
- window.dispatchEvent(event);
18151
- }
18152
-
18153
- function truncateHash(hash, startChars = 10, endChars = 8) {
18154
- if (!hash) return "";
18155
- return `${hash.slice(0, startChars)}...${hash.slice(-endChars)}`;
18156
- }
18157
-
18158
- function getErrorType(error) {
18159
- const msg = error?.message ?? "";
18160
- if (
18161
- error?.code === 4001 ||
18162
- error?.code === "ACTION_REJECTED" ||
18163
- msg.includes("rejected") ||
18164
- msg.includes("denied") ||
18165
- msg.includes("cancelled")
18166
- )
18167
- return "USER_REJECTED";
18168
- if (msg.toLowerCase().includes("insufficient")) return "INSUFFICIENT_BALANCE";
18169
- if (
18170
- msg.includes("User rejected") ||
18171
- msg.includes("User closed modal") ||
18172
- msg.includes("Connection request reset")
18173
- )
18174
- return "CONNECTION_REJECTED";
18175
- if (msg.includes("timeout")) return "TIMEOUT";
18176
- return "UNKNOWN";
18177
- }
18178
-
18179
- function getErrorMessage(error, context = {}) {
18180
- const type = getErrorType(error);
18181
-
18182
- console.error("[KwesPay] Payment error breakdown:", {
18183
- errorType: type,
18184
- message: error?.message,
18185
- code: error?.code,
18186
- data: error?.data,
18187
- reason: error?.reason,
18188
- stack: error?.stack,
18189
- raw: error,
18190
- context,
18191
- });
18192
-
18193
- if (error?.data) console.error("[KwesPay] Contract revert data:", error.data);
18194
- if (error?.transaction)
18195
- console.error("[KwesPay] Failed tx details:", error.transaction);
18196
- if (error?.receipt) console.error("[KwesPay] Tx receipt:", error.receipt);
18197
-
18198
- switch (type) {
18199
- case "USER_REJECTED":
18200
- return "You cancelled the transaction in your wallet.";
18201
- case "INSUFFICIENT_BALANCE":
18202
- return `Not enough ${context.token || "funds"} to complete this payment.`;
18203
- case "CONNECTION_REJECTED":
18204
- return "Wallet connection was cancelled.";
18205
- case "TIMEOUT":
18206
- return "Connection timed out. Please try again.";
18207
- default:
18208
- return (
18209
- error?.reason ||
18210
- error?.message ||
18211
- "Something went wrong while processing your payment."
18212
- );
18213
- }
18214
- }
18215
-
18216
- const PLATFORM_FEE_BPS = 25;
18217
-
18218
- // ── Utilities ─────────────────────────────────────────────────────────────────
18219
-
18220
- function formatUnits$1(rawBigInt, decimals) {
18221
- const divisor = BigInt(10 ** decimals);
18222
- const whole = rawBigInt / divisor;
18223
- const remainder = rawBigInt % divisor;
18224
-
18225
- if (remainder === 0n) return `${whole}`;
18226
-
18227
- const fracFull = remainder.toString().padStart(decimals, "0");
18228
-
18229
- if (whole > 0n) {
18230
- const frac = fracFull.slice(0, 4).replace(/0+$/, "");
18231
- return frac ? `${whole}.${frac}` : `${whole}`;
18232
- }
18233
-
18234
- let firstSig = -1;
18235
- for (let i = 0; i < fracFull.length; i++) {
18236
- if (fracFull[i] !== "0") {
18237
- firstSig = i;
18238
- break;
18239
- }
18240
- }
18241
- if (firstSig === -1) return `${whole}`;
18242
-
18243
- const sigSlice = fracFull.slice(firstSig, firstSig + 4).replace(/0+$/, "");
18244
- return `0.${fracFull.slice(0, firstSig) + sigSlice}`;
18245
- }
18246
-
18247
- function resolveAcceptedTokens(input) {
18248
- if (!input) return null;
18249
- if (input === "stablecoins") return STABLECOIN_SYMBOLS;
18250
- if (Array.isArray(input) && input.length)
18251
- return input.map((t) => t.toUpperCase());
18252
- return null;
18253
- }
18254
-
18255
- function isMobileDevice() {
18256
- return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
18257
- }
18258
-
18259
- // ── Widget ────────────────────────────────────────────────────────────────────
18260
-
18261
- class KwesPayWidget {
18262
- constructor(config) {
18263
- if (!config.apiKey) throw new Error("[KwesPayWidget] apiKey is required");
18264
- if (!config.vendorId)
18265
- throw new Error("[KwesPayWidget] vendorId is required");
18266
- if (!config.amount || parseFloat(config.amount) <= 0)
18267
- throw new Error("[KwesPayWidget] Valid amount is required");
18268
-
18269
- this.config = {
18270
- apiKey: config.apiKey,
18271
- vendorId: config.vendorId,
18272
- amount: parseFloat(config.amount),
18273
- currency: config.currency || DEFAULT_CONFIG.currency,
18274
- graphqlEndpoint: DEFAULT_CONFIG.graphqlEndpoint,
18275
- acceptedTokens: resolveAcceptedTokens(config.acceptedTokens),
18276
- };
18277
-
18278
- if (!Object.values(SUPPORTED_CURRENCIES).includes(this.config.currency)) {
18279
- throw new Error(
18280
- `[KwesPayWidget] Unsupported currency: ${this.config.currency}`
18281
- );
18282
- }
18283
-
18284
- this.walletService = new WalletService();
18285
- this.paymentService = new PaymentService$1(
18286
- this.config.apiKey,
18287
- this.config.graphqlEndpoint
18288
- );
18289
-
18290
- this.state = {
18291
- isOpen: false,
18292
- currentStep: 0,
18293
- selectedNetwork: null,
18294
- selectedNetworkName: "",
18295
- selectedChainId: null,
18296
- selectedRpcUrl: null,
18297
- selectedContractAddress: null,
18298
- selectedToken: null,
18299
- selectedTokenConfig: null,
18300
- vendorInfo: null,
18301
- keyAllowedNetworks: null,
18302
- keyAllowedTokens: null,
18303
- currentPayload: null,
18304
- quoteTimerInterval: null,
18305
- wcUri: null,
18306
- };
18307
-
18308
- this._init();
18309
- }
18310
-
18311
- // ── Bootstrap ───────────────────────────────────────────────────────────────
18312
-
18223
+ const DomMethods = {
18313
18224
  _init() {
18314
18225
  this._injectFonts();
18315
18226
  this._injectStyles();
18316
18227
  this._createWidgetDOM();
18317
- }
18228
+ },
18318
18229
 
18319
18230
  _injectFonts() {
18320
18231
  if (
@@ -18328,7 +18239,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18328
18239
  link.rel = "stylesheet";
18329
18240
  document.head.appendChild(link);
18330
18241
  }
18331
- }
18242
+ },
18332
18243
 
18333
18244
  _injectStyles() {
18334
18245
  if (!document.getElementById("kwespay-widget-styles")) {
@@ -18337,7 +18248,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18337
18248
  style.textContent = WIDGET_STYLES;
18338
18249
  document.head.appendChild(style);
18339
18250
  }
18340
- }
18251
+ },
18341
18252
 
18342
18253
  _createWidgetDOM() {
18343
18254
  document.getElementById("kwespay-widget-overlay")?.remove();
@@ -18364,15 +18275,16 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18364
18275
  document.body.appendChild(overlay);
18365
18276
 
18366
18277
  this._setupEventListeners();
18278
+ this._setupSwipeToClose(container);
18367
18279
 
18368
- overlay.addEventListener("click", (e) => {
18369
- if (e.target === overlay) this.close();
18370
- });
18280
+ // Clicking outside the container does NOT close the widget (intentional)
18281
+ },
18371
18282
 
18372
- // Swipe-down to close on mobile
18283
+ _setupSwipeToClose(container) {
18373
18284
  let _touchStartY = 0,
18374
18285
  _touchCurrentY = 0,
18375
18286
  _isDragging = false;
18287
+
18376
18288
  container.addEventListener(
18377
18289
  "touchstart",
18378
18290
  (e) => {
@@ -18387,6 +18299,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18387
18299
  },
18388
18300
  { passive: true }
18389
18301
  );
18302
+
18390
18303
  container.addEventListener(
18391
18304
  "touchmove",
18392
18305
  (e) => {
@@ -18397,6 +18310,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18397
18310
  },
18398
18311
  { passive: true }
18399
18312
  );
18313
+
18400
18314
  container.addEventListener("touchend", () => {
18401
18315
  if (!_isDragging) return;
18402
18316
  _isDragging = false;
@@ -18405,7 +18319,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18405
18319
  if (delta > 120) this.close();
18406
18320
  else container.style.transform = "translateY(0)";
18407
18321
  });
18408
- }
18322
+ },
18409
18323
 
18410
18324
  _setupEventListeners() {
18411
18325
  document
@@ -18514,10 +18428,83 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18514
18428
  });
18515
18429
  });
18516
18430
  }
18517
- }
18431
+ },
18432
+ };
18433
+
18434
+ const NavMethods = {
18435
+ _goToStep(stepNumber) {
18436
+ document
18437
+ .querySelectorAll(".kwespay-container .step")
18438
+ .forEach((s) => s.classList.remove("active", "exiting"));
18439
+ this._activateStep(stepNumber);
18440
+ this.state.currentStep = stepNumber;
18441
+ },
18442
+
18443
+ _activateStep(stepNumber) {
18444
+ let targetStep;
18445
+ if (stepNumber === 0.5)
18446
+ targetStep = document.getElementById("kwespay-step0-invalid");
18447
+ else if (typeof stepNumber === "string")
18448
+ targetStep = document.getElementById(`kwespay-step-${stepNumber}`);
18449
+ else targetStep = document.getElementById(`kwespay-step${stepNumber}`);
18450
+
18451
+ if (targetStep) targetStep.classList.add("active");
18452
+ else console.warn(`[KwesPayWidget] Step not found: ${stepNumber}`);
18453
+ },
18454
+
18455
+ _removeCustomStep(id) {
18456
+ document.getElementById(id)?.remove();
18457
+ },
18458
+
18459
+ _showError(title, message) {
18460
+ const titleEl = document.getElementById("kwespay-errorTitle");
18461
+ const msgEl = document.getElementById("kwespay-errorMessage");
18462
+ if (titleEl) titleEl.textContent = title;
18463
+ if (msgEl) msgEl.textContent = message;
18464
+ this._goToStep(6);
18465
+ },
18466
+
18467
+ _reset() {
18468
+ this._clearQuoteTimer();
18469
+ this._removeCustomStep("kwespay-step-wallet-picker");
18470
+ this._removeCustomStep("kwespay-step-wc");
18471
+ this.state.selectedNetwork = null;
18472
+ this.state.selectedNetworkName = "";
18473
+ this.state.selectedChainId = null;
18474
+ this.state.selectedRpcUrl = null;
18475
+ this.state.selectedContractAddress = null;
18476
+ this.state.selectedToken = null;
18477
+ this.state.selectedTokenConfig = null;
18478
+ this.state.currentPayload = null;
18479
+ this.state.wcUri = null;
18480
+ },
18481
+ };
18518
18482
 
18519
- // ── Network / token list rendering ──────────────────────────────────────────
18483
+ const APIKeyMethods = {
18484
+ async _validateAPIKey() {
18485
+ try {
18486
+ const validation = await this.paymentService.validateAPIKey();
18487
+ if (validation.valid) {
18488
+ this.state.vendorInfo = validation.vendorInfo;
18489
+ this.state.keyAllowedNetworks = validation.allowedNetworks ?? null;
18490
+ this.state.keyAllowedTokens = validation.allowedTokens ?? null;
18491
+ this._goToStep(1);
18492
+ dispatchWidgetEvent("apiKeyValidated", {
18493
+ vendorInfo: this.state.vendorInfo,
18494
+ });
18495
+ } else {
18496
+ this._goToStep(0.5);
18497
+ dispatchWidgetEvent("apiKeyInvalid", {});
18498
+ }
18499
+ } catch (err) {
18500
+ console.error("[KwesPayWidget] API key validation error:", err.message);
18501
+ this._goToStep(0.5);
18502
+ dispatchWidgetEvent("apiKeyError", { error: err.message });
18503
+ }
18504
+ },
18505
+ };
18520
18506
 
18507
+ const NetworkMethods = {
18521
18508
  _renderNetworkList() {
18522
18509
  const mainnetList = document.getElementById("kwespay-mainnetList");
18523
18510
  const testnetList = document.getElementById("kwespay-testnetList");
@@ -18579,7 +18566,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18579
18566
 
18580
18567
  if (testnetSection)
18581
18568
  testnetSection.style.display = hasTestnet ? "block" : "none";
18582
- }
18569
+ },
18583
18570
 
18584
18571
  _renderTokenList() {
18585
18572
  if (!this.state.selectedNetwork) return;
@@ -18618,7 +18605,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18618
18605
  );
18619
18606
  tokenList.appendChild(tokenItem);
18620
18607
  });
18621
- }
18608
+ },
18622
18609
 
18623
18610
  _effectiveAllowedTokens() {
18624
18611
  const keyTokens =
@@ -18628,17 +18615,64 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18628
18615
  if (!keyTokens) return configTokens;
18629
18616
  if (!configTokens) return keyTokens;
18630
18617
  return keyTokens.filter((t) => configTokens.includes(t));
18631
- }
18618
+ },
18632
18619
 
18633
- // ── Wallet connection flow ───────────────────────────────────────────────────
18620
+ async _handleNetworkSelection(key, network) {
18621
+ this.state.selectedNetwork = key;
18622
+ this.state.selectedNetworkName = network.name;
18623
+ this.state.selectedChainId = network.chainId;
18624
+ this.state.selectedRpcUrl = network.rpcUrl;
18625
+ this.state.selectedContractAddress = network.contractAddress;
18626
+ this.state.selectedToken = null;
18627
+ this.state.selectedTokenConfig = null;
18634
18628
 
18629
+ const nameEl = document.getElementById("kwespay-selectedNetworkName");
18630
+ if (nameEl)
18631
+ nameEl.textContent = `${network.name} ${
18632
+ network.type === "mainnet" ? "Mainnet" : "Testnet"
18633
+ }`;
18634
+
18635
+ const iconEl = document.getElementById("kwespay-selectedNetworkIcon");
18636
+ if (iconEl)
18637
+ iconEl.innerHTML = `<img src="${network.logo}" alt="${network.name}" />`;
18638
+
18639
+ const continueBtn = document.getElementById(
18640
+ "kwespay-continueToWalletConnect"
18641
+ );
18642
+ if (continueBtn) continueBtn.disabled = true;
18643
+
18644
+ this._renderTokenList();
18645
+ this._goToStep(2);
18646
+ },
18647
+
18648
+ _handleTokenSelection(token) {
18649
+ document
18650
+ .querySelectorAll("#kwespay-tokenList .token-item")
18651
+ .forEach((item) => item.classList.remove("selected"));
18652
+ document
18653
+ .querySelector(
18654
+ `#kwespay-tokenList .token-item[data-token-symbol="${token.symbol}"]`
18655
+ )
18656
+ ?.classList.add("selected");
18657
+
18658
+ this.state.selectedToken = token.symbol;
18659
+ this.state.selectedTokenConfig = token;
18660
+
18661
+ const continueBtn = document.getElementById(
18662
+ "kwespay-continueToWalletConnect"
18663
+ );
18664
+ if (continueBtn) continueBtn.disabled = false;
18665
+ },
18666
+ };
18667
+
18668
+ const WalletMethods = {
18635
18669
  async _handleWalletConnection() {
18636
18670
  if (!this.state.selectedToken || !this.state.selectedTokenConfig) {
18637
18671
  alert("Please select a token first");
18638
18672
  return;
18639
18673
  }
18640
18674
  this._renderWalletPickerStep();
18641
- }
18675
+ },
18642
18676
 
18643
18677
  _renderWalletPickerStep() {
18644
18678
  this._removeCustomStep("kwespay-step-wallet-picker");
@@ -18736,7 +18770,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18736
18770
  list.appendChild(item);
18737
18771
  });
18738
18772
  });
18739
- }
18773
+ },
18740
18774
 
18741
18775
  async _connectInjectedProvider(index) {
18742
18776
  try {
@@ -18751,7 +18785,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18751
18785
  );
18752
18786
  dispatchWidgetEvent("walletConnectionError", { error: err.message });
18753
18787
  }
18754
- }
18788
+ },
18755
18789
 
18756
18790
  async _startWalletConnect() {
18757
18791
  this._renderWCStep();
@@ -18767,7 +18801,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18767
18801
  this._renderWCMobileStep();
18768
18802
  }
18769
18803
 
18770
- // Pass the target chain so MetaMask starts the WC session on the right
18804
+ // Pass the target chain so the wallet starts the WC session on the right
18771
18805
  // network — without this it defaults to its last-used WC chain (Mainnet).
18772
18806
  await this.walletService.connectWalletConnect(this.state.selectedChainId);
18773
18807
  } catch (err) {
@@ -18782,9 +18816,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18782
18816
  err.message || "WalletConnect failed."
18783
18817
  );
18784
18818
  }
18785
- }
18786
-
18787
- // ── WalletConnect UI ─────────────────────────────────────────────────────────
18819
+ },
18788
18820
 
18789
18821
  _renderWCStep() {
18790
18822
  this._removeCustomStep("kwespay-step-wc");
@@ -18817,7 +18849,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18817
18849
  });
18818
18850
 
18819
18851
  this._goToStep("wc");
18820
- }
18852
+ },
18821
18853
 
18822
18854
  _wcDesktopHTML() {
18823
18855
  return `
@@ -18839,7 +18871,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18839
18871
  <p style="font-size:11px;color:var(--kp-muted);font-family:var(--kp-mono);text-align:center">Waiting for wallet connection…</p>
18840
18872
  </div>
18841
18873
  `;
18842
- }
18874
+ },
18843
18875
 
18844
18876
  _wcMobileHTML() {
18845
18877
  const wallets = this.walletService.getMobileWallets();
@@ -18887,7 +18919,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18887
18919
  </p>
18888
18920
  </div>
18889
18921
  `;
18890
- }
18922
+ },
18891
18923
 
18892
18924
  _renderWCMobileStep() {
18893
18925
  const list = document.getElementById("kwespay-mobile-wallet-list");
@@ -18911,7 +18943,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18911
18943
  }
18912
18944
  });
18913
18945
  });
18914
- }
18946
+ },
18915
18947
 
18916
18948
  _onWCUri(uri) {
18917
18949
  this.state.wcUri = uri;
@@ -18928,7 +18960,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18928
18960
  } else {
18929
18961
  this._renderQRCode(uri);
18930
18962
  }
18931
- }
18963
+ },
18932
18964
 
18933
18965
  _renderQRCode(uri) {
18934
18966
  const canvas = document.getElementById("kwespay-qr-canvas");
@@ -18963,18 +18995,18 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18963
18995
  <p style="font-size:10px;color:#888;font-family:monospace">Use Copy URI button</p>
18964
18996
  </div>
18965
18997
  `;
18966
- }
18998
+ },
18967
18999
 
18968
19000
  _onWCConnected() {
18969
19001
  const connection = this.walletService._connectionResult();
18970
19002
  this._removeCustomStep("kwespay-step-wc");
18971
19003
  this._onWalletConnected(connection);
18972
- }
19004
+ },
18973
19005
 
18974
19006
  _onWCDisconnected() {
18975
19007
  this._removeCustomStep("kwespay-step-wc");
18976
19008
  this._goToStep(2);
18977
- }
19009
+ },
18978
19010
 
18979
19011
  async _onWalletConnected(connection) {
18980
19012
  const addressEl = document.getElementById("kwespay-connectedWalletAddress");
@@ -18991,7 +19023,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
18991
19023
  const proceedBtn = document.getElementById("kwespay-proceedToPayment");
18992
19024
 
18993
19025
  if (cryptoLine) {
18994
- cryptoLine.textContent = "Getting the best rate for you…";
19026
+ cryptoLine.textContent = "loading…";
18995
19027
  cryptoLine.classList.add("loading");
18996
19028
  }
18997
19029
  if (timerEl) timerEl.style.display = "none";
@@ -19013,83 +19045,39 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19013
19045
  }
19014
19046
 
19015
19047
  dispatchWidgetEvent("walletConnected", { address: connection.address });
19016
- }
19017
-
19018
- // ── API key validation ───────────────────────────────────────────────────────
19019
-
19020
- async _validateAPIKey() {
19021
- try {
19022
- const validation = await this.paymentService.validateAPIKey();
19023
- if (validation.valid) {
19024
- this.state.vendorInfo = validation.vendorInfo;
19025
- this.state.keyAllowedNetworks = validation.allowedNetworks ?? null;
19026
- this.state.keyAllowedTokens = validation.allowedTokens ?? null;
19027
- this._goToStep(1);
19028
- dispatchWidgetEvent("apiKeyValidated", {
19029
- vendorInfo: this.state.vendorInfo,
19030
- });
19031
- } else {
19032
- this._goToStep(0.5);
19033
- dispatchWidgetEvent("apiKeyInvalid", {});
19034
- }
19035
- } catch (err) {
19036
- console.error("[KwesPayWidget] API key validation error:", err.message);
19037
- this._goToStep(0.5);
19038
- dispatchWidgetEvent("apiKeyError", { error: err.message });
19039
- }
19040
- }
19041
-
19042
- // ── Network / token selection ────────────────────────────────────────────────
19048
+ },
19049
+ };
19043
19050
 
19044
- async _handleNetworkSelection(key, network) {
19045
- this.state.selectedNetwork = key;
19046
- this.state.selectedNetworkName = network.name;
19047
- this.state.selectedChainId = network.chainId;
19048
- this.state.selectedRpcUrl = network.rpcUrl;
19049
- this.state.selectedContractAddress = network.contractAddress;
19050
- this.state.selectedToken = null;
19051
- this.state.selectedTokenConfig = null;
19051
+ const PLATFORM_FEE_BPS = 25;
19052
19052
 
19053
- const nameEl = document.getElementById("kwespay-selectedNetworkName");
19054
- if (nameEl)
19055
- nameEl.textContent = `${network.name} ${
19056
- network.type === "mainnet" ? "Mainnet" : "Testnet"
19057
- }`;
19053
+ function formatUnits$1(rawBigInt, decimals) {
19054
+ const divisor = BigInt(10 ** decimals);
19055
+ const whole = rawBigInt / divisor;
19056
+ const remainder = rawBigInt % divisor;
19058
19057
 
19059
- const iconEl = document.getElementById("kwespay-selectedNetworkIcon");
19060
- if (iconEl)
19061
- iconEl.innerHTML = `<img src="${network.logo}" alt="${network.name}" />`;
19058
+ if (remainder === 0n) return `${whole}`;
19062
19059
 
19063
- const continueBtn = document.getElementById(
19064
- "kwespay-continueToWalletConnect"
19065
- );
19066
- if (continueBtn) continueBtn.disabled = true;
19060
+ const fracFull = remainder.toString().padStart(decimals, "0");
19067
19061
 
19068
- this._renderTokenList();
19069
- this._goToStep(2);
19062
+ if (whole > 0n) {
19063
+ const frac = fracFull.slice(0, 4).replace(/0+$/, "");
19064
+ return frac ? `${whole}.${frac}` : `${whole}`;
19070
19065
  }
19071
19066
 
19072
- _handleTokenSelection(token) {
19073
- document
19074
- .querySelectorAll("#kwespay-tokenList .token-item")
19075
- .forEach((item) => item.classList.remove("selected"));
19076
- document
19077
- .querySelector(
19078
- `#kwespay-tokenList .token-item[data-token-symbol="${token.symbol}"]`
19079
- )
19080
- ?.classList.add("selected");
19081
-
19082
- this.state.selectedToken = token.symbol;
19083
- this.state.selectedTokenConfig = token;
19084
-
19085
- const continueBtn = document.getElementById(
19086
- "kwespay-continueToWalletConnect"
19087
- );
19088
- if (continueBtn) continueBtn.disabled = false;
19067
+ let firstSig = -1;
19068
+ for (let i = 0; i < fracFull.length; i++) {
19069
+ if (fracFull[i] !== "0") {
19070
+ firstSig = i;
19071
+ break;
19072
+ }
19089
19073
  }
19074
+ if (firstSig === -1) return `${whole}`;
19090
19075
 
19091
- // ── Quote / review step ──────────────────────────────────────────────────────
19076
+ const sigSlice = fracFull.slice(firstSig, firstSig + 4).replace(/0+$/, "");
19077
+ return `0.${fracFull.slice(0, firstSig) + sigSlice}`;
19078
+ }
19092
19079
 
19080
+ const QuoteMethods = {
19093
19081
  async _loadReviewStep() {
19094
19082
  this._clearQuoteTimer();
19095
19083
 
@@ -19101,7 +19089,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19101
19089
  const proceedBtn = document.getElementById("kwespay-proceedToPayment");
19102
19090
 
19103
19091
  if (cryptoLine) {
19104
- cryptoLine.textContent = "Getting the best rate for you…";
19092
+ cryptoLine.textContent = "loading…";
19105
19093
  cryptoLine.classList.add("loading");
19106
19094
  }
19107
19095
  if (timerEl) timerEl.style.display = "none";
@@ -19146,7 +19134,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19146
19134
  }
19147
19135
  if (proceedBtn) proceedBtn.disabled = true;
19148
19136
  }
19149
- }
19137
+ },
19150
19138
 
19151
19139
  _startQuoteTimer(expiresAt) {
19152
19140
  const timerEl = document.getElementById("kwespay-quoteTimer");
@@ -19167,6 +19155,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19167
19155
  "kp-quote-timer" +
19168
19156
  (secs <= 30 ? " urgent" : "") +
19169
19157
  (secs <= 0 ? " expired" : "");
19158
+
19170
19159
  if (secs <= 0) {
19171
19160
  timerText.textContent = "Refreshing your rate…";
19172
19161
  const proceedBtn = document.getElementById("kwespay-proceedToPayment");
@@ -19175,19 +19164,20 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19175
19164
  this._loadReviewStep();
19176
19165
  }
19177
19166
  };
19167
+
19178
19168
  update();
19179
19169
  this.state.quoteTimerInterval = setInterval(update, 1000);
19180
- }
19170
+ },
19181
19171
 
19182
19172
  _clearQuoteTimer() {
19183
19173
  if (this.state.quoteTimerInterval) {
19184
19174
  clearInterval(this.state.quoteTimerInterval);
19185
19175
  this.state.quoteTimerInterval = null;
19186
19176
  }
19187
- }
19188
-
19189
- // ── Payment processing ───────────────────────────────────────────────────────
19177
+ },
19178
+ };
19190
19179
 
19180
+ const PaymentMethods = {
19191
19181
  async _handlePaymentProcessing() {
19192
19182
  if (!this.state.currentPayload) {
19193
19183
  this._showError(
@@ -19216,9 +19206,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19216
19206
 
19217
19207
  if (!provider) throw new Error("No wallet provider");
19218
19208
 
19219
- // ── Session liveness check ───────────────────────────────────────────────
19220
- // Must come first — a stale WC session throws on any RPC call, and we want
19221
- // a clean "reconnect" error rather than a cryptic provider error downstream.
19209
+
19222
19210
  const alive = await this.walletService.isSessionAlive();
19223
19211
  if (!alive) {
19224
19212
  console.error(
@@ -19232,22 +19220,17 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19232
19220
  throw err;
19233
19221
  }
19234
19222
 
19235
- // ── Show mobile helper banner ────────────────────────────────────────────
19223
+
19236
19224
  if (isMobile) {
19237
19225
  document
19238
19226
  .getElementById("kwespay-mobileTransactionInstruction")
19239
19227
  ?.style.setProperty("display", "flex");
19240
19228
  }
19241
19229
 
19242
- // ── Chain validation ─────────────────────────────────────────────────────
19243
19230
  if (strictMobile) {
19244
- // On mobile WC, eth_chainId goes to the wallet via the WC relay and
19245
- // reflects the wallet's actual active chain — always call it live.
19246
- // Never rely on session namespace account ordering (MetaMask puts
19247
- // Mainnet first regardless of which chain the user is on).
19231
+
19248
19232
  await this._assertMobileChain(provider, targetChainId);
19249
19233
  } else {
19250
- // Desktop / injected — eth_chainId is reliable, switch if needed
19251
19234
  const rawChain = await provider.request({ method: "eth_chainId" });
19252
19235
  const currentChainId = parseInt(rawChain, 16);
19253
19236
 
@@ -19268,21 +19251,17 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19268
19251
  this.state.selectedToken,
19269
19252
  this.state.selectedTokenConfig.decimals
19270
19253
  );
19271
- console.log("[KwesPay] Network switched");
19254
+ console.log("[KwesPay] Network switched");
19272
19255
  }
19273
19256
  }
19274
19257
 
19275
- // ── Open wallet BEFORE sending tx (mobile WC only) ──────────────────────
19276
- // We deep-link to the wallet before the JSON-RPC request lands so the
19277
- // approval prompt appears immediately without the user having to manually
19278
- // switch apps.
19258
+
19279
19259
  if (strictMobile) {
19280
19260
  setStatus(
19281
19261
  "Opening your wallet…",
19282
19262
  "Approve the payment in your wallet."
19283
19263
  );
19284
19264
  this.walletService._openWalletForApproval();
19285
- // Give the OS time to foreground the wallet app before the RPC lands
19286
19265
  await new Promise((r) => setTimeout(r, 700));
19287
19266
  } else {
19288
19267
  setStatus(
@@ -19291,19 +19270,18 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19291
19270
  );
19292
19271
  }
19293
19272
 
19294
- // ── Send transaction ─────────────────────────────────────────────────────
19295
19273
  const receipt = await this.paymentService.createPayment({
19296
19274
  payload: this.state.currentPayload,
19297
19275
  walletProvider: provider,
19298
19276
  onStatusUpdate: setStatus,
19299
19277
  });
19300
19278
 
19301
- // ── Cleanup ──────────────────────────────────────────────────────────────
19279
+
19302
19280
  document
19303
19281
  .getElementById("kwespay-mobileTransactionInstruction")
19304
19282
  ?.style.setProperty("display", "none");
19305
19283
 
19306
- // ── Success UI ───────────────────────────────────────────────────────────
19284
+
19307
19285
  const decimals = this.state.selectedTokenConfig?.decimals ?? 6;
19308
19286
  const amountBig = BigInt(this.state.currentPayload.amountBaseUnits);
19309
19287
  const cryptoDisplay = `${formatUnits$1(amountBig, decimals)} ${
@@ -19366,7 +19344,7 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19366
19344
  this._showError(title, message);
19367
19345
  dispatchWidgetEvent("paymentError", { error: message, errorType });
19368
19346
  }
19369
- }
19347
+ },
19370
19348
 
19371
19349
  /**
19372
19350
  * Confirm the wallet is on the target chain before sending a transaction.
@@ -19374,9 +19352,6 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19374
19352
  * Throws WRONG_NETWORK if the chain never matches.
19375
19353
  *
19376
19354
  * MOBILE WC ONLY — never call this on desktop/injected.
19377
- *
19378
- * @param {object} provider
19379
- * @param {number} targetChainId
19380
19355
  */
19381
19356
  async _assertMobileChain(provider, targetChainId) {
19382
19357
  const MAX_ATTEMPTS = 3;
@@ -19410,25 +19385,14 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19410
19385
  }
19411
19386
  }
19412
19387
 
19413
- // All attempts failed — wallet is on wrong chain
19414
19388
  const err = new Error(
19415
19389
  `Please switch to ${this.state.selectedNetworkName} in your wallet and try again.`
19416
19390
  );
19417
19391
  err.code = "WRONG_NETWORK";
19418
19392
  throw err;
19419
- }
19393
+ },
19394
+
19420
19395
 
19421
- /**
19422
- * Switch network for desktop/injected providers and poll until confirmed.
19423
- * MUST NOT be called in strictMobile mode (mobile WC doesn't support
19424
- * wallet_switchEthereumChain reliably).
19425
- *
19426
- * @param {number} chainId
19427
- * @param {string} networkName
19428
- * @param {string} rpcUrl
19429
- * @param {string} tokenSymbol
19430
- * @param {number} tokenDecimals
19431
- */
19432
19396
  async _switchNetworkSafe(
19433
19397
  chainId,
19434
19398
  networkName,
@@ -19436,7 +19400,6 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19436
19400
  tokenSymbol,
19437
19401
  tokenDecimals
19438
19402
  ) {
19439
- // Capture before any async boundary — prevents `this` loss in callbacks
19440
19403
  const switchNetwork = this.walletService.switchNetwork;
19441
19404
  const provider = this.walletService.getProvider();
19442
19405
 
@@ -19459,12 +19422,11 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19459
19422
 
19460
19423
  const targetHex = toHex(chainId);
19461
19424
 
19462
- // Check current chain — skip if already correct
19463
19425
  try {
19464
19426
  const currentHex = toHex(
19465
19427
  await provider.request({ method: "eth_chainId" })
19466
19428
  );
19467
- if (currentHex && currentHex === targetHex) return; // already correct, silent return
19429
+ if (currentHex && currentHex === targetHex) return;
19468
19430
  } catch (err) {
19469
19431
  console.warn(
19470
19432
  "[KwesPay] Could not read chainId before switch:",
@@ -19472,7 +19434,6 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19472
19434
  );
19473
19435
  }
19474
19436
 
19475
- // Issue the switch
19476
19437
  try {
19477
19438
  await switchNetwork(
19478
19439
  chainId,
@@ -19518,43 +19479,66 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19518
19479
  `Could not confirm network switch to ${networkName} after 15s. ` +
19519
19480
  `Please switch manually in your wallet and try again.`
19520
19481
  );
19521
- }
19482
+ },
19483
+ };
19522
19484
 
19523
- // ── Step navigation ──────────────────────────────────────────────────────────
19485
+ function resolveAcceptedTokens(input) {
19486
+ if (!input) return null;
19487
+ if (input === "stablecoins") return STABLECOIN_SYMBOLS;
19488
+ if (Array.isArray(input) && input.length)
19489
+ return input.map((t) => t.toUpperCase());
19490
+ return null;
19491
+ }
19524
19492
 
19525
- _goToStep(stepNumber) {
19526
- document
19527
- .querySelectorAll(".kwespay-container .step")
19528
- .forEach((s) => s.classList.remove("active", "exiting"));
19529
- this._activateStep(stepNumber);
19530
- this.state.currentStep = stepNumber;
19531
- }
19493
+ class KwesPayWidget {
19494
+ constructor(config) {
19495
+ if (!config.apiKey) throw new Error("[KwesPayWidget] apiKey is required");
19496
+ if (!config.vendorId)
19497
+ throw new Error("[KwesPayWidget] vendorId is required");
19498
+ if (!config.amount || parseFloat(config.amount) <= 0)
19499
+ throw new Error("[KwesPayWidget] Valid amount is required");
19532
19500
 
19533
- _activateStep(stepNumber) {
19534
- let targetStep;
19535
- if (stepNumber === 0.5)
19536
- targetStep = document.getElementById("kwespay-step0-invalid");
19537
- else if (typeof stepNumber === "string")
19538
- targetStep = document.getElementById(`kwespay-step-${stepNumber}`);
19539
- else targetStep = document.getElementById(`kwespay-step${stepNumber}`);
19501
+ this.config = {
19502
+ apiKey: config.apiKey,
19503
+ vendorId: config.vendorId,
19504
+ amount: parseFloat(config.amount),
19505
+ currency: config.currency || DEFAULT_CONFIG.currency,
19506
+ graphqlEndpoint: DEFAULT_CONFIG.graphqlEndpoint,
19507
+ acceptedTokens: resolveAcceptedTokens(config.acceptedTokens),
19508
+ };
19540
19509
 
19541
- if (targetStep) targetStep.classList.add("active");
19542
- else console.warn(`[KwesPayWidget] Step not found: ${stepNumber}`);
19543
- }
19510
+ if (!Object.values(SUPPORTED_CURRENCIES).includes(this.config.currency)) {
19511
+ throw new Error(
19512
+ `[KwesPayWidget] Unsupported currency: ${this.config.currency}`
19513
+ );
19514
+ }
19544
19515
 
19545
- _removeCustomStep(id) {
19546
- document.getElementById(id)?.remove();
19547
- }
19516
+ this.walletService = new WalletService();
19517
+ this.paymentService = new PaymentService$1(
19518
+ this.config.apiKey,
19519
+ this.config.graphqlEndpoint
19520
+ );
19548
19521
 
19549
- _showError(title, message) {
19550
- const titleEl = document.getElementById("kwespay-errorTitle");
19551
- const msgEl = document.getElementById("kwespay-errorMessage");
19552
- if (titleEl) titleEl.textContent = title;
19553
- if (msgEl) msgEl.textContent = message;
19554
- this._goToStep(6);
19555
- }
19522
+ this.state = {
19523
+ isOpen: false,
19524
+ currentStep: 0,
19525
+ selectedNetwork: null,
19526
+ selectedNetworkName: "",
19527
+ selectedChainId: null,
19528
+ selectedRpcUrl: null,
19529
+ selectedContractAddress: null,
19530
+ selectedToken: null,
19531
+ selectedTokenConfig: null,
19532
+ vendorInfo: null,
19533
+ keyAllowedNetworks: null,
19534
+ keyAllowedTokens: null,
19535
+ currentPayload: null,
19536
+ quoteTimerInterval: null,
19537
+ wcUri: null,
19538
+ };
19556
19539
 
19557
- // ── Public API ───────────────────────────────────────────────────────────────
19540
+ this._init();
19541
+ }
19558
19542
 
19559
19543
  async open() {
19560
19544
  const overlay = document.getElementById("kwespay-widget-overlay");
@@ -19631,21 +19615,6 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19631
19615
  return { ...this.state, config: { ...this.config } };
19632
19616
  }
19633
19617
 
19634
- _reset() {
19635
- this._clearQuoteTimer();
19636
- this._removeCustomStep("kwespay-step-wallet-picker");
19637
- this._removeCustomStep("kwespay-step-wc");
19638
- this.state.selectedNetwork = null;
19639
- this.state.selectedNetworkName = "";
19640
- this.state.selectedChainId = null;
19641
- this.state.selectedRpcUrl = null;
19642
- this.state.selectedContractAddress = null;
19643
- this.state.selectedToken = null;
19644
- this.state.selectedTokenConfig = null;
19645
- this.state.currentPayload = null;
19646
- this.state.wcUri = null;
19647
- }
19648
-
19649
19618
  destroy() {
19650
19619
  this._clearQuoteTimer();
19651
19620
  document.body.classList.remove("kwespay-open");
@@ -19660,6 +19629,17 @@ ${e.length}`,n=new TextEncoder().encode(t+e);return "0x"+toString$1(keccak_256$3
19660
19629
  }
19661
19630
  }
19662
19631
 
19632
+ Object.assign(
19633
+ KwesPayWidget.prototype,
19634
+ DomMethods,
19635
+ NavMethods,
19636
+ APIKeyMethods,
19637
+ NetworkMethods,
19638
+ WalletMethods,
19639
+ QuoteMethods,
19640
+ PaymentMethods
19641
+ );
19642
+
19663
19643
  /**
19664
19644
  * KwesPay Widget - Main entry point
19665
19645
  * @module @kwespay/widget