astra-sdk-web 1.1.8 → 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.
@@ -219,6 +219,12 @@ function mergeConfig(userConfig) {
219
219
  }
220
220
 
221
221
  // src/services/kycApiService.ts
222
+ var COMPLETED_STEPS = {
223
+ INITIATED: "initiated",
224
+ FACE: "face_scan",
225
+ DOCS: "document_upload",
226
+ COMPLETED: "completed"
227
+ };
222
228
  var KycApiService = class {
223
229
  config;
224
230
  constructor(config) {
@@ -336,6 +342,36 @@ var KycApiService = class {
336
342
  throw new Error(`Document upload failed: ${message}`);
337
343
  }
338
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
+ }
339
375
  /**
340
376
  * Check if session is active, throw error if not
341
377
  */
@@ -610,6 +646,7 @@ function DocumentUploadModal({ onComplete }) {
610
646
  const navigate = reactRouterDom.useNavigate();
611
647
  const { apiService } = useKycContext();
612
648
  const [sessionError, setSessionError] = React.useState(null);
649
+ const [kycCompleted, setKycCompleted] = React.useState(false);
613
650
  const {
614
651
  state,
615
652
  setState,
@@ -625,6 +662,15 @@ function DocumentUploadModal({ onComplete }) {
625
662
  throw new Error("API service not initialized");
626
663
  }
627
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
+ }
628
674
  },
629
675
  onUpload: (file, docType) => {
630
676
  if (onComplete) {
@@ -641,7 +687,24 @@ function DocumentUploadModal({ onComplete }) {
641
687
  const checkSession = async () => {
642
688
  if (!apiService) return;
643
689
  try {
644
- await apiService.checkSessionActive();
690
+ const statusResponse = await apiService.getSessionStatus();
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
+ }
696
+ if (status !== "ACTIVE") {
697
+ throw new Error("Session expired or inactive");
698
+ }
699
+ if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
700
+ if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
701
+ setKycCompleted(true);
702
+ return;
703
+ }
704
+ }
705
+ if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
706
+ console.warn("Face scan not completed, but in document upload modal");
707
+ }
645
708
  setSessionError(null);
646
709
  } catch (error) {
647
710
  const message = error.message || "Session expired or inactive";
@@ -653,6 +716,14 @@ function DocumentUploadModal({ onComplete }) {
653
716
  };
654
717
  checkSession();
655
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
+ }
656
727
  if (sessionError) {
657
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: [
658
729
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
@@ -1087,7 +1158,8 @@ var FaceMeshService = class {
1087
1158
  }
1088
1159
  }
1089
1160
  } else if (state.stage === "DONE") {
1090
- if (absYaw < centerThreshold && insideGuide) {
1161
+ const reducedThreshold = 0.08;
1162
+ if (absYaw < reducedThreshold) {
1091
1163
  state.centerHold += 1;
1092
1164
  if (state.centerHold >= holdFramesCenter && !state.snapTriggered) {
1093
1165
  state.snapTriggered = true;
@@ -1101,11 +1173,7 @@ var FaceMeshService = class {
1101
1173
  } else {
1102
1174
  state.centerHold = 0;
1103
1175
  if (this.callbacks.onLivenessUpdate) {
1104
- if (!insideGuide) {
1105
- this.callbacks.onLivenessUpdate(state.stage, "Center your face inside the circle");
1106
- } else {
1107
- this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
1108
- }
1176
+ this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
1109
1177
  }
1110
1178
  }
1111
1179
  }
@@ -1267,7 +1335,7 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
1267
1335
  }, []);
1268
1336
  const handleFaceCapture = React.useCallback(async () => {
1269
1337
  if (!videoRef.current) return;
1270
- const centerThreshold = 0.05;
1338
+ const reducedThreshold = 0.08;
1271
1339
  const currentAbsYaw = livenessStateRef.current.currentAbsYaw;
1272
1340
  if (currentAbsYaw === null || currentAbsYaw === void 0) {
1273
1341
  setState((prev) => ({
@@ -1276,7 +1344,7 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
1276
1344
  }));
1277
1345
  return;
1278
1346
  }
1279
- if (currentAbsYaw >= centerThreshold) {
1347
+ if (currentAbsYaw >= reducedThreshold) {
1280
1348
  setState((prev) => ({
1281
1349
  ...prev,
1282
1350
  livenessInstruction: "Please look straight at the camera before capturing"
@@ -1492,6 +1560,9 @@ function FaceScanModal({ onComplete }) {
1492
1560
  const { apiService } = useKycContext();
1493
1561
  const [sessionError, setSessionError] = React.useState(null);
1494
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);
1495
1566
  const { videoRef, cameraReady, stopCamera } = useCamera();
1496
1567
  const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
1497
1568
  onFaceUpload: async (blob) => {
@@ -1504,10 +1575,12 @@ function FaceScanModal({ onComplete }) {
1504
1575
  const errorMessage = error?.message || "";
1505
1576
  const errorData = error?.errorData || {};
1506
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);
1507
1579
  setToast({
1508
- message: "Face already registered",
1580
+ message: "Face already registered. Click Retry to register again.",
1509
1581
  type: "warning"
1510
1582
  });
1583
+ setState((prev) => ({ ...prev, loading: false }));
1511
1584
  return;
1512
1585
  }
1513
1586
  throw error;
@@ -1523,7 +1596,25 @@ function FaceScanModal({ onComplete }) {
1523
1596
  const checkSession = async () => {
1524
1597
  if (!apiService) return;
1525
1598
  try {
1526
- await apiService.checkSessionActive();
1599
+ const statusResponse = await apiService.getSessionStatus();
1600
+ const { completed_steps, next_step, status } = statusResponse.data;
1601
+ if (status !== "ACTIVE") {
1602
+ throw new Error("Session expired or inactive");
1603
+ }
1604
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
1605
+ setKycCompleted(true);
1606
+ return;
1607
+ }
1608
+ if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
1609
+ setState((prev) => ({ ...prev, showDocumentUpload: true }));
1610
+ return;
1611
+ }
1612
+ if (next_step !== COMPLETED_STEPS.FACE && next_step !== COMPLETED_STEPS.INITIATED) {
1613
+ if (next_step === COMPLETED_STEPS.DOCS) {
1614
+ setState((prev) => ({ ...prev, showDocumentUpload: true }));
1615
+ return;
1616
+ }
1617
+ }
1527
1618
  setSessionError(null);
1528
1619
  } catch (error) {
1529
1620
  const message = error.message || "Session expired or inactive";
@@ -1534,7 +1625,7 @@ function FaceScanModal({ onComplete }) {
1534
1625
  }
1535
1626
  };
1536
1627
  checkSession();
1537
- }, [apiService, navigate]);
1628
+ }, [apiService, navigate, setState]);
1538
1629
  React.useEffect(() => {
1539
1630
  setState((prev) => ({ ...prev, cameraReady }));
1540
1631
  }, [cameraReady, setState]);
@@ -1561,7 +1652,47 @@ function FaceScanModal({ onComplete }) {
1561
1652
  }
1562
1653
  }
1563
1654
  }, [cameraReady, apiService]);
1564
- 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 = () => {
1565
1696
  stopCamera();
1566
1697
  setState({
1567
1698
  cameraReady: false,
@@ -1587,6 +1718,14 @@ function FaceScanModal({ onComplete }) {
1587
1718
  window.location.reload();
1588
1719
  }, 100);
1589
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
+ }
1590
1729
  if (state.showDocumentUpload) {
1591
1730
  return /* @__PURE__ */ jsxRuntime.jsx(
1592
1731
  DocumentUploadModal_default,
@@ -1649,13 +1788,23 @@ function FaceScanModal({ onComplete }) {
1649
1788
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
1650
1789
  ] })
1651
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
+ ),
1652
1801
  /* @__PURE__ */ jsxRuntime.jsx(
1653
1802
  "button",
1654
1803
  {
1655
1804
  type: "button",
1656
- disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
1805
+ disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
1657
1806
  onClick: handleFaceCapture,
1658
- 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"}`,
1659
1808
  children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
1660
1809
  }
1661
1810
  ),
@@ -1663,9 +1812,9 @@ function FaceScanModal({ onComplete }) {
1663
1812
  "button",
1664
1813
  {
1665
1814
  type: "button",
1666
- onClick: handleRetry,
1667
- disabled: state.loading,
1668
- 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]"}`,
1669
1818
  children: "Restart"
1670
1819
  }
1671
1820
  )