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.
@@ -89,15 +89,21 @@ var KycApiService = class {
89
89
  );
90
90
  if (!response.ok) {
91
91
  const errorData = await response.json().catch(() => ({}));
92
- const message = errorData?.message || `Face upload failed with status ${response.status}`;
92
+ const message = errorData?.message || errorData?.errorData?.message || `Face upload failed with status ${response.status}`;
93
93
  const error = new Error(message);
94
94
  error.statusCode = response.status;
95
95
  error.errorData = errorData;
96
+ if (errorData.statusCode) {
97
+ error.statusCode = errorData.statusCode;
98
+ }
96
99
  throw error;
97
100
  }
98
101
  const data = await response.json();
99
102
  return data;
100
103
  } catch (error) {
104
+ if (error.message && !error.message.includes("Face upload failed")) {
105
+ throw error;
106
+ }
101
107
  const message = error?.message || "Face upload failed";
102
108
  throw new Error(`Face upload failed: ${message}`);
103
109
  }
@@ -137,6 +143,36 @@ var KycApiService = class {
137
143
  throw new Error(`Document upload failed: ${message}`);
138
144
  }
139
145
  }
146
+ /**
147
+ * Retry session - resets the session to allow face registration again
148
+ */
149
+ async retrySession() {
150
+ const deviceType = this.config.deviceType || this.detectDeviceType();
151
+ try {
152
+ const response = await fetch(
153
+ `${this.config.apiBaseUrl}/api/v2/dashboard/merchant/onsite/session/${this.config.sessionId}/retry`,
154
+ {
155
+ method: "POST",
156
+ headers: {
157
+ "x-server-key": this.config.serverKey,
158
+ "device-type": deviceType,
159
+ "Content-Type": "application/json"
160
+ },
161
+ credentials: "include"
162
+ }
163
+ );
164
+ if (!response.ok) {
165
+ const errorData = await response.json().catch(() => ({}));
166
+ const message = errorData?.message || `Retry failed with status ${response.status}`;
167
+ throw new Error(message);
168
+ }
169
+ const data = await response.json();
170
+ return data;
171
+ } catch (error) {
172
+ const message = error?.message || "Retry failed";
173
+ throw new Error(`Retry failed: ${message}`);
174
+ }
175
+ }
140
176
  /**
141
177
  * Check if session is active, throw error if not
142
178
  */
@@ -411,6 +447,7 @@ function DocumentUploadModal({ onComplete }) {
411
447
  const navigate = reactRouterDom.useNavigate();
412
448
  const { apiService } = useKycContext();
413
449
  const [sessionError, setSessionError] = React.useState(null);
450
+ const [kycCompleted, setKycCompleted] = React.useState(false);
414
451
  const {
415
452
  state,
416
453
  setState,
@@ -426,6 +463,15 @@ function DocumentUploadModal({ onComplete }) {
426
463
  throw new Error("API service not initialized");
427
464
  }
428
465
  await apiService.uploadDocument(blob, docType);
466
+ try {
467
+ const statusResponse = await apiService.getSessionStatus();
468
+ const { completed_steps, status } = statusResponse.data;
469
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
470
+ setKycCompleted(true);
471
+ }
472
+ } catch (error) {
473
+ console.error("Error checking completion status:", error);
474
+ }
429
475
  },
430
476
  onUpload: (file, docType) => {
431
477
  if (onComplete) {
@@ -444,11 +490,18 @@ function DocumentUploadModal({ onComplete }) {
444
490
  try {
445
491
  const statusResponse = await apiService.getSessionStatus();
446
492
  const { completed_steps, next_step, status } = statusResponse.data;
493
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
494
+ setKycCompleted(true);
495
+ return;
496
+ }
447
497
  if (status !== "ACTIVE") {
448
498
  throw new Error("Session expired or inactive");
449
499
  }
450
500
  if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
451
- console.log("Document already uploaded");
501
+ if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
502
+ setKycCompleted(true);
503
+ return;
504
+ }
452
505
  }
453
506
  if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
454
507
  console.warn("Face scan not completed, but in document upload modal");
@@ -464,6 +517,14 @@ function DocumentUploadModal({ onComplete }) {
464
517
  };
465
518
  checkSession();
466
519
  }, [apiService, navigate]);
520
+ if (kycCompleted) {
521
+ 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: [
522
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
523
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
524
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
525
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
526
+ ] }) }) });
527
+ }
467
528
  if (sessionError) {
468
529
  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: [
469
530
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
@@ -1114,8 +1175,29 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
1114
1175
  livenessInstruction: "Face captured and uploaded successfully!",
1115
1176
  loading: false
1116
1177
  }));
1178
+ if (callbacks?.onFaceCaptureComplete) {
1179
+ callbacks.onFaceCaptureComplete(dataUrl);
1180
+ }
1181
+ setTimeout(() => {
1182
+ setState((prev) => ({ ...prev, showDocumentUpload: true }));
1183
+ }, 500);
1117
1184
  } catch (uploadError) {
1118
- throw new Error(uploadError.message || "Failed to upload face scan");
1185
+ if (uploadError.message === "FACE_ALREADY_REGISTERED" || uploadError.isFaceAlreadyRegistered) {
1186
+ setState((prev) => ({
1187
+ ...prev,
1188
+ loading: false,
1189
+ allStepsCompleted: false,
1190
+ showDocumentUpload: false,
1191
+ livenessInstruction: "Face already registered. Please click Retry to register again."
1192
+ }));
1193
+ return;
1194
+ }
1195
+ setState((prev) => ({
1196
+ ...prev,
1197
+ livenessInstruction: uploadError.message || "Error capturing image. Please try again.",
1198
+ loading: false
1199
+ }));
1200
+ throw uploadError;
1119
1201
  }
1120
1202
  } else {
1121
1203
  setState((prev) => ({
@@ -1125,13 +1207,13 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
1125
1207
  livenessInstruction: "Face captured successfully!",
1126
1208
  loading: false
1127
1209
  }));
1210
+ if (callbacks?.onFaceCaptureComplete) {
1211
+ callbacks.onFaceCaptureComplete(dataUrl);
1212
+ }
1213
+ setTimeout(() => {
1214
+ setState((prev) => ({ ...prev, showDocumentUpload: true }));
1215
+ }, 500);
1128
1216
  }
1129
- if (callbacks?.onFaceCaptureComplete) {
1130
- callbacks.onFaceCaptureComplete(dataUrl);
1131
- }
1132
- setTimeout(() => {
1133
- setState((prev) => ({ ...prev, showDocumentUpload: true }));
1134
- }, 500);
1135
1217
  } catch (err) {
1136
1218
  console.error("Error capturing image:", err);
1137
1219
  setState((prev) => ({
@@ -1300,6 +1382,9 @@ function FaceScanModal({ onComplete }) {
1300
1382
  const { apiService } = useKycContext();
1301
1383
  const [sessionError, setSessionError] = React.useState(null);
1302
1384
  const [toast, setToast] = React.useState(null);
1385
+ const [showRetryButton, setShowRetryButton] = React.useState(false);
1386
+ const [isRetrying, setIsRetrying] = React.useState(false);
1387
+ const [kycCompleted, setKycCompleted] = React.useState(false);
1303
1388
  const { videoRef, cameraReady, stopCamera } = useCamera();
1304
1389
  const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
1305
1390
  onFaceUpload: async (blob) => {
@@ -1311,12 +1396,18 @@ function FaceScanModal({ onComplete }) {
1311
1396
  } catch (error) {
1312
1397
  const errorMessage = error?.message || "";
1313
1398
  const errorData = error?.errorData || {};
1314
- if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
1399
+ const statusCode = error?.statusCode;
1400
+ const isFaceAlreadyRegistered = errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || statusCode === 500 && errorMessage.includes("Face already registered");
1401
+ if (isFaceAlreadyRegistered) {
1402
+ setShowRetryButton(true);
1315
1403
  setToast({
1316
- message: "Face already registered",
1404
+ message: "Face already registered. Click Retry to register again.",
1317
1405
  type: "warning"
1318
1406
  });
1319
- return;
1407
+ setState((prev) => ({ ...prev, loading: false, allStepsCompleted: false, showDocumentUpload: false }));
1408
+ const faceRegisteredError = new Error("FACE_ALREADY_REGISTERED");
1409
+ faceRegisteredError.isFaceAlreadyRegistered = true;
1410
+ throw faceRegisteredError;
1320
1411
  }
1321
1412
  throw error;
1322
1413
  }
@@ -1336,6 +1427,10 @@ function FaceScanModal({ onComplete }) {
1336
1427
  if (status !== "ACTIVE") {
1337
1428
  throw new Error("Session expired or inactive");
1338
1429
  }
1430
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
1431
+ setKycCompleted(true);
1432
+ return;
1433
+ }
1339
1434
  if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
1340
1435
  setState((prev) => ({ ...prev, showDocumentUpload: true }));
1341
1436
  return;
@@ -1383,7 +1478,47 @@ function FaceScanModal({ onComplete }) {
1383
1478
  }
1384
1479
  }
1385
1480
  }, [cameraReady, apiService]);
1386
- const handleRetry = () => {
1481
+ const handleRetry = async () => {
1482
+ if (!apiService || isRetrying) return;
1483
+ setIsRetrying(true);
1484
+ setShowRetryButton(false);
1485
+ setToast(null);
1486
+ try {
1487
+ await apiService.retrySession();
1488
+ stopCamera();
1489
+ setState({
1490
+ cameraReady: false,
1491
+ livenessStage: "CENTER",
1492
+ livenessReady: false,
1493
+ livenessFailed: false,
1494
+ modelLoading: true,
1495
+ modelLoaded: false,
1496
+ livenessInstruction: "Look straight at the camera",
1497
+ loading: false,
1498
+ allStepsCompleted: false,
1499
+ capturedImage: null,
1500
+ showDocumentUpload: false
1501
+ });
1502
+ refs.centerHold.current = 0;
1503
+ refs.leftHold.current = 0;
1504
+ refs.rightHold.current = 0;
1505
+ refs.snapTriggered.current = false;
1506
+ refs.lastResultsAt.current = 0;
1507
+ refs.modelLoaded.current = false;
1508
+ refs.livenessFailed.current = false;
1509
+ setTimeout(() => {
1510
+ window.location.reload();
1511
+ }, 500);
1512
+ } catch (error) {
1513
+ setIsRetrying(false);
1514
+ setShowRetryButton(true);
1515
+ setToast({
1516
+ message: error?.message || "Retry failed. Please try again.",
1517
+ type: "error"
1518
+ });
1519
+ }
1520
+ };
1521
+ const handleRestart = () => {
1387
1522
  stopCamera();
1388
1523
  setState({
1389
1524
  cameraReady: false,
@@ -1409,6 +1544,14 @@ function FaceScanModal({ onComplete }) {
1409
1544
  window.location.reload();
1410
1545
  }, 100);
1411
1546
  };
1547
+ if (kycCompleted) {
1548
+ 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: [
1549
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
1550
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
1551
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
1552
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
1553
+ ] }) }) });
1554
+ }
1412
1555
  if (state.showDocumentUpload) {
1413
1556
  return /* @__PURE__ */ jsxRuntime.jsx(
1414
1557
  DocumentUploadModal_default,
@@ -1471,13 +1614,23 @@ function FaceScanModal({ onComplete }) {
1471
1614
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
1472
1615
  ] })
1473
1616
  ] }),
1617
+ showRetryButton && /* @__PURE__ */ jsxRuntime.jsx(
1618
+ "button",
1619
+ {
1620
+ type: "button",
1621
+ onClick: handleRetry,
1622
+ disabled: isRetrying || state.loading,
1623
+ 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",
1624
+ children: isRetrying ? "Retrying..." : "Retry Face Registration"
1625
+ }
1626
+ ),
1474
1627
  /* @__PURE__ */ jsxRuntime.jsx(
1475
1628
  "button",
1476
1629
  {
1477
1630
  type: "button",
1478
- disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
1631
+ disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
1479
1632
  onClick: handleFaceCapture,
1480
- 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"}`,
1633
+ 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"}`,
1481
1634
  children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
1482
1635
  }
1483
1636
  ),
@@ -1485,9 +1638,9 @@ function FaceScanModal({ onComplete }) {
1485
1638
  "button",
1486
1639
  {
1487
1640
  type: "button",
1488
- onClick: handleRetry,
1489
- disabled: state.loading,
1490
- 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]"}`,
1641
+ onClick: handleRestart,
1642
+ disabled: state.loading || isRetrying,
1643
+ 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]"}`,
1491
1644
  children: "Restart"
1492
1645
  }
1493
1646
  )