astra-sdk-web 1.1.9 → 1.1.11

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.
@@ -294,15 +294,21 @@ var KycApiService = class {
294
294
  );
295
295
  if (!response.ok) {
296
296
  const errorData = await response.json().catch(() => ({}));
297
- const message = errorData?.message || `Face upload failed with status ${response.status}`;
297
+ const message = errorData?.message || errorData?.errorData?.message || `Face upload failed with status ${response.status}`;
298
298
  const error = new Error(message);
299
299
  error.statusCode = response.status;
300
300
  error.errorData = errorData;
301
+ if (errorData.statusCode) {
302
+ error.statusCode = errorData.statusCode;
303
+ }
301
304
  throw error;
302
305
  }
303
306
  const data = await response.json();
304
307
  return data;
305
308
  } catch (error) {
309
+ if (error.message && !error.message.includes("Face upload failed")) {
310
+ throw error;
311
+ }
306
312
  const message = error?.message || "Face upload failed";
307
313
  throw new Error(`Face upload failed: ${message}`);
308
314
  }
@@ -342,6 +348,36 @@ var KycApiService = class {
342
348
  throw new Error(`Document upload failed: ${message}`);
343
349
  }
344
350
  }
351
+ /**
352
+ * Retry session - resets the session to allow face registration again
353
+ */
354
+ async retrySession() {
355
+ const deviceType = this.config.deviceType || this.detectDeviceType();
356
+ try {
357
+ const response = await fetch(
358
+ `${this.config.apiBaseUrl}/api/v2/dashboard/merchant/onsite/session/${this.config.sessionId}/retry`,
359
+ {
360
+ method: "POST",
361
+ headers: {
362
+ "x-server-key": this.config.serverKey,
363
+ "device-type": deviceType,
364
+ "Content-Type": "application/json"
365
+ },
366
+ credentials: "include"
367
+ }
368
+ );
369
+ if (!response.ok) {
370
+ const errorData = await response.json().catch(() => ({}));
371
+ const message = errorData?.message || `Retry failed with status ${response.status}`;
372
+ throw new Error(message);
373
+ }
374
+ const data = await response.json();
375
+ return data;
376
+ } catch (error) {
377
+ const message = error?.message || "Retry failed";
378
+ throw new Error(`Retry failed: ${message}`);
379
+ }
380
+ }
345
381
  /**
346
382
  * Check if session is active, throw error if not
347
383
  */
@@ -616,6 +652,7 @@ function DocumentUploadModal({ onComplete }) {
616
652
  const navigate = reactRouterDom.useNavigate();
617
653
  const { apiService } = useKycContext();
618
654
  const [sessionError, setSessionError] = React.useState(null);
655
+ const [kycCompleted, setKycCompleted] = React.useState(false);
619
656
  const {
620
657
  state,
621
658
  setState,
@@ -631,6 +668,15 @@ function DocumentUploadModal({ onComplete }) {
631
668
  throw new Error("API service not initialized");
632
669
  }
633
670
  await apiService.uploadDocument(blob, docType);
671
+ try {
672
+ const statusResponse = await apiService.getSessionStatus();
673
+ const { completed_steps, status } = statusResponse.data;
674
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
675
+ setKycCompleted(true);
676
+ }
677
+ } catch (error) {
678
+ console.error("Error checking completion status:", error);
679
+ }
634
680
  },
635
681
  onUpload: (file, docType) => {
636
682
  if (onComplete) {
@@ -649,11 +695,18 @@ function DocumentUploadModal({ onComplete }) {
649
695
  try {
650
696
  const statusResponse = await apiService.getSessionStatus();
651
697
  const { completed_steps, next_step, status } = statusResponse.data;
698
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
699
+ setKycCompleted(true);
700
+ return;
701
+ }
652
702
  if (status !== "ACTIVE") {
653
703
  throw new Error("Session expired or inactive");
654
704
  }
655
705
  if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
656
- console.log("Document already uploaded");
706
+ if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
707
+ setKycCompleted(true);
708
+ return;
709
+ }
657
710
  }
658
711
  if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
659
712
  console.warn("Face scan not completed, but in document upload modal");
@@ -669,6 +722,14 @@ function DocumentUploadModal({ onComplete }) {
669
722
  };
670
723
  checkSession();
671
724
  }, [apiService, navigate]);
725
+ if (kycCompleted) {
726
+ 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: [
727
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
728
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
729
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
730
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
731
+ ] }) }) });
732
+ }
672
733
  if (sessionError) {
673
734
  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
735
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
@@ -1319,8 +1380,29 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
1319
1380
  livenessInstruction: "Face captured and uploaded successfully!",
1320
1381
  loading: false
1321
1382
  }));
1383
+ if (callbacks?.onFaceCaptureComplete) {
1384
+ callbacks.onFaceCaptureComplete(dataUrl);
1385
+ }
1386
+ setTimeout(() => {
1387
+ setState((prev) => ({ ...prev, showDocumentUpload: true }));
1388
+ }, 500);
1322
1389
  } catch (uploadError) {
1323
- throw new Error(uploadError.message || "Failed to upload face scan");
1390
+ if (uploadError.message === "FACE_ALREADY_REGISTERED" || uploadError.isFaceAlreadyRegistered) {
1391
+ setState((prev) => ({
1392
+ ...prev,
1393
+ loading: false,
1394
+ allStepsCompleted: false,
1395
+ showDocumentUpload: false,
1396
+ livenessInstruction: "Face already registered. Please click Retry to register again."
1397
+ }));
1398
+ return;
1399
+ }
1400
+ setState((prev) => ({
1401
+ ...prev,
1402
+ livenessInstruction: uploadError.message || "Error capturing image. Please try again.",
1403
+ loading: false
1404
+ }));
1405
+ throw uploadError;
1324
1406
  }
1325
1407
  } else {
1326
1408
  setState((prev) => ({
@@ -1330,13 +1412,13 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
1330
1412
  livenessInstruction: "Face captured successfully!",
1331
1413
  loading: false
1332
1414
  }));
1415
+ if (callbacks?.onFaceCaptureComplete) {
1416
+ callbacks.onFaceCaptureComplete(dataUrl);
1417
+ }
1418
+ setTimeout(() => {
1419
+ setState((prev) => ({ ...prev, showDocumentUpload: true }));
1420
+ }, 500);
1333
1421
  }
1334
- if (callbacks?.onFaceCaptureComplete) {
1335
- callbacks.onFaceCaptureComplete(dataUrl);
1336
- }
1337
- setTimeout(() => {
1338
- setState((prev) => ({ ...prev, showDocumentUpload: true }));
1339
- }, 500);
1340
1422
  } catch (err) {
1341
1423
  console.error("Error capturing image:", err);
1342
1424
  setState((prev) => ({
@@ -1505,6 +1587,9 @@ function FaceScanModal({ onComplete }) {
1505
1587
  const { apiService } = useKycContext();
1506
1588
  const [sessionError, setSessionError] = React.useState(null);
1507
1589
  const [toast, setToast] = React.useState(null);
1590
+ const [showRetryButton, setShowRetryButton] = React.useState(false);
1591
+ const [isRetrying, setIsRetrying] = React.useState(false);
1592
+ const [kycCompleted, setKycCompleted] = React.useState(false);
1508
1593
  const { videoRef, cameraReady, stopCamera } = useCamera();
1509
1594
  const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
1510
1595
  onFaceUpload: async (blob) => {
@@ -1516,12 +1601,18 @@ function FaceScanModal({ onComplete }) {
1516
1601
  } catch (error) {
1517
1602
  const errorMessage = error?.message || "";
1518
1603
  const errorData = error?.errorData || {};
1519
- if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
1604
+ const statusCode = error?.statusCode;
1605
+ const isFaceAlreadyRegistered = errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || statusCode === 500 && errorMessage.includes("Face already registered");
1606
+ if (isFaceAlreadyRegistered) {
1607
+ setShowRetryButton(true);
1520
1608
  setToast({
1521
- message: "Face already registered",
1609
+ message: "Face already registered. Click Retry to register again.",
1522
1610
  type: "warning"
1523
1611
  });
1524
- return;
1612
+ setState((prev) => ({ ...prev, loading: false, allStepsCompleted: false, showDocumentUpload: false }));
1613
+ const faceRegisteredError = new Error("FACE_ALREADY_REGISTERED");
1614
+ faceRegisteredError.isFaceAlreadyRegistered = true;
1615
+ throw faceRegisteredError;
1525
1616
  }
1526
1617
  throw error;
1527
1618
  }
@@ -1541,6 +1632,10 @@ function FaceScanModal({ onComplete }) {
1541
1632
  if (status !== "ACTIVE") {
1542
1633
  throw new Error("Session expired or inactive");
1543
1634
  }
1635
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
1636
+ setKycCompleted(true);
1637
+ return;
1638
+ }
1544
1639
  if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
1545
1640
  setState((prev) => ({ ...prev, showDocumentUpload: true }));
1546
1641
  return;
@@ -1588,7 +1683,47 @@ function FaceScanModal({ onComplete }) {
1588
1683
  }
1589
1684
  }
1590
1685
  }, [cameraReady, apiService]);
1591
- const handleRetry = () => {
1686
+ const handleRetry = async () => {
1687
+ if (!apiService || isRetrying) return;
1688
+ setIsRetrying(true);
1689
+ setShowRetryButton(false);
1690
+ setToast(null);
1691
+ try {
1692
+ await apiService.retrySession();
1693
+ stopCamera();
1694
+ setState({
1695
+ cameraReady: false,
1696
+ livenessStage: "CENTER",
1697
+ livenessReady: false,
1698
+ livenessFailed: false,
1699
+ modelLoading: true,
1700
+ modelLoaded: false,
1701
+ livenessInstruction: "Look straight at the camera",
1702
+ loading: false,
1703
+ allStepsCompleted: false,
1704
+ capturedImage: null,
1705
+ showDocumentUpload: false
1706
+ });
1707
+ refs.centerHold.current = 0;
1708
+ refs.leftHold.current = 0;
1709
+ refs.rightHold.current = 0;
1710
+ refs.snapTriggered.current = false;
1711
+ refs.lastResultsAt.current = 0;
1712
+ refs.modelLoaded.current = false;
1713
+ refs.livenessFailed.current = false;
1714
+ setTimeout(() => {
1715
+ window.location.reload();
1716
+ }, 500);
1717
+ } catch (error) {
1718
+ setIsRetrying(false);
1719
+ setShowRetryButton(true);
1720
+ setToast({
1721
+ message: error?.message || "Retry failed. Please try again.",
1722
+ type: "error"
1723
+ });
1724
+ }
1725
+ };
1726
+ const handleRestart = () => {
1592
1727
  stopCamera();
1593
1728
  setState({
1594
1729
  cameraReady: false,
@@ -1614,6 +1749,14 @@ function FaceScanModal({ onComplete }) {
1614
1749
  window.location.reload();
1615
1750
  }, 100);
1616
1751
  };
1752
+ if (kycCompleted) {
1753
+ 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: [
1754
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
1755
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
1756
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
1757
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
1758
+ ] }) }) });
1759
+ }
1617
1760
  if (state.showDocumentUpload) {
1618
1761
  return /* @__PURE__ */ jsxRuntime.jsx(
1619
1762
  DocumentUploadModal_default,
@@ -1676,13 +1819,23 @@ function FaceScanModal({ onComplete }) {
1676
1819
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
1677
1820
  ] })
1678
1821
  ] }),
1822
+ showRetryButton && /* @__PURE__ */ jsxRuntime.jsx(
1823
+ "button",
1824
+ {
1825
+ type: "button",
1826
+ onClick: handleRetry,
1827
+ disabled: isRetrying || state.loading,
1828
+ 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",
1829
+ children: isRetrying ? "Retrying..." : "Retry Face Registration"
1830
+ }
1831
+ ),
1679
1832
  /* @__PURE__ */ jsxRuntime.jsx(
1680
1833
  "button",
1681
1834
  {
1682
1835
  type: "button",
1683
- disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
1836
+ disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
1684
1837
  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"}`,
1838
+ 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
1839
  children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
1687
1840
  }
1688
1841
  ),
@@ -1690,9 +1843,9 @@ function FaceScanModal({ onComplete }) {
1690
1843
  "button",
1691
1844
  {
1692
1845
  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]"}`,
1846
+ onClick: handleRestart,
1847
+ disabled: state.loading || isRetrying,
1848
+ 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
1849
  children: "Restart"
1697
1850
  }
1698
1851
  )