astra-sdk-web 1.1.9 → 1.1.10

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.
@@ -342,6 +342,36 @@ var KycApiService = class {
342
342
  throw new Error(`Document upload failed: ${message}`);
343
343
  }
344
344
  }
345
+ /**
346
+ * Retry session - resets the session to allow face registration again
347
+ */
348
+ async retrySession() {
349
+ const deviceType = this.config.deviceType || this.detectDeviceType();
350
+ try {
351
+ const response = await fetch(
352
+ `${this.config.apiBaseUrl}/api/v2/dashboard/merchant/onsite/session/${this.config.sessionId}/retry`,
353
+ {
354
+ method: "POST",
355
+ headers: {
356
+ "x-server-key": this.config.serverKey,
357
+ "device-type": deviceType,
358
+ "Content-Type": "application/json"
359
+ },
360
+ credentials: "include"
361
+ }
362
+ );
363
+ if (!response.ok) {
364
+ const errorData = await response.json().catch(() => ({}));
365
+ const message = errorData?.message || `Retry failed with status ${response.status}`;
366
+ throw new Error(message);
367
+ }
368
+ const data = await response.json();
369
+ return data;
370
+ } catch (error) {
371
+ const message = error?.message || "Retry failed";
372
+ throw new Error(`Retry failed: ${message}`);
373
+ }
374
+ }
345
375
  /**
346
376
  * Check if session is active, throw error if not
347
377
  */
@@ -616,6 +646,7 @@ function DocumentUploadModal({ onComplete }) {
616
646
  const navigate = reactRouterDom.useNavigate();
617
647
  const { apiService } = useKycContext();
618
648
  const [sessionError, setSessionError] = React.useState(null);
649
+ const [kycCompleted, setKycCompleted] = React.useState(false);
619
650
  const {
620
651
  state,
621
652
  setState,
@@ -631,6 +662,15 @@ function DocumentUploadModal({ onComplete }) {
631
662
  throw new Error("API service not initialized");
632
663
  }
633
664
  await apiService.uploadDocument(blob, docType);
665
+ try {
666
+ const statusResponse = await apiService.getSessionStatus();
667
+ const { completed_steps, status } = statusResponse.data;
668
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
669
+ setKycCompleted(true);
670
+ }
671
+ } catch (error) {
672
+ console.error("Error checking completion status:", error);
673
+ }
634
674
  },
635
675
  onUpload: (file, docType) => {
636
676
  if (onComplete) {
@@ -649,11 +689,18 @@ function DocumentUploadModal({ onComplete }) {
649
689
  try {
650
690
  const statusResponse = await apiService.getSessionStatus();
651
691
  const { completed_steps, next_step, status } = statusResponse.data;
692
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
693
+ setKycCompleted(true);
694
+ return;
695
+ }
652
696
  if (status !== "ACTIVE") {
653
697
  throw new Error("Session expired or inactive");
654
698
  }
655
699
  if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
656
- console.log("Document already uploaded");
700
+ if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
701
+ setKycCompleted(true);
702
+ return;
703
+ }
657
704
  }
658
705
  if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
659
706
  console.warn("Face scan not completed, but in document upload modal");
@@ -669,6 +716,14 @@ function DocumentUploadModal({ onComplete }) {
669
716
  };
670
717
  checkSession();
671
718
  }, [apiService, navigate]);
719
+ if (kycCompleted) {
720
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black p-5 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
721
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
722
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
723
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
724
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
725
+ ] }) }) });
726
+ }
672
727
  if (sessionError) {
673
728
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black p-4 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
674
729
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
@@ -1505,6 +1560,9 @@ function FaceScanModal({ onComplete }) {
1505
1560
  const { apiService } = useKycContext();
1506
1561
  const [sessionError, setSessionError] = React.useState(null);
1507
1562
  const [toast, setToast] = React.useState(null);
1563
+ const [showRetryButton, setShowRetryButton] = React.useState(false);
1564
+ const [isRetrying, setIsRetrying] = React.useState(false);
1565
+ const [kycCompleted, setKycCompleted] = React.useState(false);
1508
1566
  const { videoRef, cameraReady, stopCamera } = useCamera();
1509
1567
  const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
1510
1568
  onFaceUpload: async (blob) => {
@@ -1517,10 +1575,12 @@ function FaceScanModal({ onComplete }) {
1517
1575
  const errorMessage = error?.message || "";
1518
1576
  const errorData = error?.errorData || {};
1519
1577
  if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
1578
+ setShowRetryButton(true);
1520
1579
  setToast({
1521
- message: "Face already registered",
1580
+ message: "Face already registered. Click Retry to register again.",
1522
1581
  type: "warning"
1523
1582
  });
1583
+ setState((prev) => ({ ...prev, loading: false }));
1524
1584
  return;
1525
1585
  }
1526
1586
  throw error;
@@ -1541,6 +1601,10 @@ function FaceScanModal({ onComplete }) {
1541
1601
  if (status !== "ACTIVE") {
1542
1602
  throw new Error("Session expired or inactive");
1543
1603
  }
1604
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
1605
+ setKycCompleted(true);
1606
+ return;
1607
+ }
1544
1608
  if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
1545
1609
  setState((prev) => ({ ...prev, showDocumentUpload: true }));
1546
1610
  return;
@@ -1588,7 +1652,47 @@ function FaceScanModal({ onComplete }) {
1588
1652
  }
1589
1653
  }
1590
1654
  }, [cameraReady, apiService]);
1591
- const handleRetry = () => {
1655
+ const handleRetry = async () => {
1656
+ if (!apiService || isRetrying) return;
1657
+ setIsRetrying(true);
1658
+ setShowRetryButton(false);
1659
+ setToast(null);
1660
+ try {
1661
+ await apiService.retrySession();
1662
+ stopCamera();
1663
+ setState({
1664
+ cameraReady: false,
1665
+ livenessStage: "CENTER",
1666
+ livenessReady: false,
1667
+ livenessFailed: false,
1668
+ modelLoading: true,
1669
+ modelLoaded: false,
1670
+ livenessInstruction: "Look straight at the camera",
1671
+ loading: false,
1672
+ allStepsCompleted: false,
1673
+ capturedImage: null,
1674
+ showDocumentUpload: false
1675
+ });
1676
+ refs.centerHold.current = 0;
1677
+ refs.leftHold.current = 0;
1678
+ refs.rightHold.current = 0;
1679
+ refs.snapTriggered.current = false;
1680
+ refs.lastResultsAt.current = 0;
1681
+ refs.modelLoaded.current = false;
1682
+ refs.livenessFailed.current = false;
1683
+ setTimeout(() => {
1684
+ window.location.reload();
1685
+ }, 500);
1686
+ } catch (error) {
1687
+ setIsRetrying(false);
1688
+ setShowRetryButton(true);
1689
+ setToast({
1690
+ message: error?.message || "Retry failed. Please try again.",
1691
+ type: "error"
1692
+ });
1693
+ }
1694
+ };
1695
+ const handleRestart = () => {
1592
1696
  stopCamera();
1593
1697
  setState({
1594
1698
  cameraReady: false,
@@ -1614,6 +1718,14 @@ function FaceScanModal({ onComplete }) {
1614
1718
  window.location.reload();
1615
1719
  }, 100);
1616
1720
  };
1721
+ if (kycCompleted) {
1722
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black p-5 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
1723
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
1724
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
1725
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
1726
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
1727
+ ] }) }) });
1728
+ }
1617
1729
  if (state.showDocumentUpload) {
1618
1730
  return /* @__PURE__ */ jsxRuntime.jsx(
1619
1731
  DocumentUploadModal_default,
@@ -1676,13 +1788,23 @@ function FaceScanModal({ onComplete }) {
1676
1788
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
1677
1789
  ] })
1678
1790
  ] }),
1791
+ showRetryButton && /* @__PURE__ */ jsxRuntime.jsx(
1792
+ "button",
1793
+ {
1794
+ type: "button",
1795
+ onClick: handleRetry,
1796
+ disabled: isRetrying || state.loading,
1797
+ className: "py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors bg-[#f59e0b] text-[#0b0f17] cursor-pointer hover:bg-[#d97706] disabled:opacity-50 disabled:cursor-not-allowed",
1798
+ children: isRetrying ? "Retrying..." : "Retry Face Registration"
1799
+ }
1800
+ ),
1679
1801
  /* @__PURE__ */ jsxRuntime.jsx(
1680
1802
  "button",
1681
1803
  {
1682
1804
  type: "button",
1683
- disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
1805
+ disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
1684
1806
  onClick: handleFaceCapture,
1685
- className: `py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors ${state.cameraReady && !state.loading && (state.livenessFailed || state.livenessStage === "DONE") ? "bg-[#22c55e] text-[#0b0f17] cursor-pointer hover:bg-[#16a34a]" : "bg-[#374151] text-[#e5e7eb] cursor-not-allowed"}`,
1807
+ className: `py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors ${state.cameraReady && !state.loading && (state.livenessFailed || state.livenessStage === "DONE") && !showRetryButton ? "bg-[#22c55e] text-[#0b0f17] cursor-pointer hover:bg-[#16a34a]" : "bg-[#374151] text-[#e5e7eb] cursor-not-allowed"}`,
1686
1808
  children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
1687
1809
  }
1688
1810
  ),
@@ -1690,9 +1812,9 @@ function FaceScanModal({ onComplete }) {
1690
1812
  "button",
1691
1813
  {
1692
1814
  type: "button",
1693
- onClick: handleRetry,
1694
- disabled: state.loading,
1695
- className: `py-3 px-4 rounded-[10px] text-[15px] font-semibold border-none w-full transition-colors ${state.loading ? "bg-[#374151] text-[#e5e7eb] cursor-not-allowed opacity-50" : "bg-[#374151] text-[#e5e7eb] cursor-pointer hover:bg-[#4b5563]"}`,
1815
+ onClick: handleRestart,
1816
+ disabled: state.loading || isRetrying,
1817
+ className: `py-3 px-4 rounded-[10px] text-[15px] font-semibold border-none w-full transition-colors ${state.loading || isRetrying ? "bg-[#374151] text-[#e5e7eb] cursor-not-allowed opacity-50" : "bg-[#374151] text-[#e5e7eb] cursor-pointer hover:bg-[#4b5563]"}`,
1696
1818
  children: "Restart"
1697
1819
  }
1698
1820
  )