@myazahq/kyc-sdk-react-native 2.0.2 → 2.1.0
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.
- package/README.md +111 -8
- package/android/build.gradle +6 -0
- package/android/src/main/AndroidManifest.xml +14 -4
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaEmrtd.kt +358 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +88 -1
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaRectDetector.kt +34 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +118 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/Jp2Decoder.kt +117 -0
- package/app.plugin.js +102 -13
- package/ios/HybridMyazaEmrtd.swift +447 -0
- package/ios/HybridMyazaFaceDetector.swift +104 -2
- package/ios/HybridMyazaRectDetector.swift +103 -0
- package/ios/HybridMyazaTextRecognizer.swift +111 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/KycSdkReactNative+autolinking.cmake +87 -0
- package/nitrogen/generated/android/KycSdkReactNative+autolinking.gradle +27 -0
- package/nitrogen/generated/android/KycSdkReactNativeOnLoad.cpp +102 -0
- package/nitrogen/generated/android/KycSdkReactNativeOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JDetectedRect.hpp +81 -0
- package/nitrogen/generated/android/c++/JEmrtdApduResponse.hpp +61 -0
- package/nitrogen/generated/android/c++/JEmrtdTagInfo.hpp +61 -0
- package/nitrogen/generated/android/c++/JFaceResult.hpp +113 -0
- package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.cpp +176 -0
- package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +60 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.cpp +60 -0
- package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +80 -0
- package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JTextResult.hpp +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/DetectedRect.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdApduResponse.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdTagInfo.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/FaceResult.kt +121 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaEmrtdSpec.kt +103 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +55 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaRectDetectorSpec.kt +55 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +60 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/KycSdkReactNativeOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/TextResult.kt +51 -0
- package/nitrogen/generated/ios/KycSdkReactNative+autolinking.rb +62 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +143 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +423 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Umbrella.hpp +81 -0
- package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.mm +57 -0
- package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.swift +62 -0
- package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.hpp +184 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +87 -0
- package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.hpp +87 -0
- package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +98 -0
- package/nitrogen/generated/ios/swift/DetectedRect.swift +59 -0
- package/nitrogen/generated/ios/swift/EmrtdApduResponse.swift +34 -0
- package/nitrogen/generated/ios/swift/EmrtdTagInfo.swift +34 -0
- package/nitrogen/generated/ios/swift/FaceResult.swift +99 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_EmrtdApduResponse.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_EmrtdTagInfo.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_TextResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec.swift +67 -0
- package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec_cxx.swift +317 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +143 -0
- package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec_cxx.swift +143 -0
- package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +162 -0
- package/nitrogen/generated/ios/swift/TextResult.swift +35 -0
- package/nitrogen/generated/shared/c++/DetectedRect.hpp +107 -0
- package/nitrogen/generated/shared/c++/EmrtdApduResponse.hpp +87 -0
- package/nitrogen/generated/shared/c++/EmrtdTagInfo.hpp +87 -0
- package/nitrogen/generated/shared/c++/FaceResult.hpp +139 -0
- package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.cpp +33 -0
- package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +70 -0
- package/nitrogen/generated/shared/c++/TextResult.hpp +84 -0
- package/package.json +16 -4
- package/src/MyazaKYC.tsx +115 -22
- package/src/capture/documentIdentity.ts +153 -0
- package/src/capture/documentSignals.ts +177 -0
- package/src/capture/documentTextGate.ts +171 -0
- package/src/capture/hints.ts +57 -0
- package/src/capture/index.ts +24 -0
- package/src/capture/rectDetector.ts +84 -0
- package/src/capture/rectGate.ts +91 -0
- package/src/capture/useAutoCapture.ts +198 -0
- package/src/components/CameraViewfinder.tsx +226 -70
- package/src/components/ContactCodeEntry.tsx +139 -0
- package/src/components/CountryField.tsx +87 -0
- package/src/components/CountryFlag.tsx +15 -15
- package/src/components/DialCodePicker.tsx +186 -0
- package/src/components/DocumentCropper.tsx +1 -1
- package/src/components/DocumentReview.tsx +142 -0
- package/src/components/DocumentReviewSide.tsx +135 -0
- package/src/components/DocumentReviewZoom.tsx +119 -0
- package/src/components/ExpiryCountdown.tsx +52 -0
- package/src/components/FlashOverlay.tsx +156 -0
- package/src/components/Icon.tsx +67 -1
- package/src/components/KycFlow.tsx +66 -94
- package/src/components/KycSheet.tsx +148 -18
- package/src/components/MediaSourceSheet.tsx +163 -0
- package/src/components/MyazaButton.tsx +14 -2
- package/src/components/MyazaDateField.tsx +240 -0
- package/src/components/MyazaInput.tsx +73 -26
- package/src/components/MyazaSelect.tsx +283 -0
- package/src/components/MyazaWordmark.tsx +33 -0
- package/src/components/OptionRow.tsx +132 -0
- package/src/components/PhoneNumberInput.tsx +149 -0
- package/src/components/PoweredBy.tsx +89 -0
- package/src/components/ReadyPrimer.tsx +149 -0
- package/src/components/StepView.tsx +91 -0
- package/src/components/Typography.tsx +18 -3
- package/src/components/VerifiedNotice.tsx +38 -0
- package/src/components/WhatsAppIcon.tsx +29 -0
- package/src/components/WorkflowGate.tsx +128 -0
- package/src/components/brand-font.ts +113 -0
- package/src/components/flashHoleGeometry.ts +35 -0
- package/src/components/flow/FatalConfigError.tsx +56 -0
- package/src/components/fonts.ts +2 -19
- package/src/components/readyPrimerContent.ts +52 -0
- package/src/components/runtime.tsx +38 -43
- package/src/components/stepHeaderMeta.tsx +87 -0
- package/src/components/theme-provider.tsx +89 -0
- package/src/components/useWorkflowMount.ts +116 -0
- package/src/components/viewfinder/CardGuide.tsx +60 -0
- package/src/components/viewfinder/DocumentGhost.tsx +149 -0
- package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
- package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
- package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
- package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
- package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
- package/src/config/brand.ts +97 -0
- package/src/config/business.ts +179 -0
- package/src/config/businessSteps.ts +105 -0
- package/src/config/contact.ts +159 -0
- package/src/config/countryNames.g.ts +254 -0
- package/src/config/currencyFlags.ts +144 -0
- package/src/config/font-resolve.ts +78 -0
- package/src/config/idTypes.ts +96 -1
- package/src/config/keyPeople.ts +152 -0
- package/src/config/phone.ts +39 -0
- package/src/config/proofOfAddress.ts +75 -0
- package/src/config/questionnaire.ts +153 -0
- package/src/config/regions.ts +118 -0
- package/src/config/registrationHint.ts +87 -0
- package/src/config/stepOrder.ts +118 -0
- package/src/config/theme.ts +38 -2
- package/src/config/workflowMerge.ts +124 -0
- package/src/emrtd/bac.ts +128 -0
- package/src/emrtd/bytes.ts +127 -0
- package/src/emrtd/crypto.ts +163 -0
- package/src/emrtd/der.ts +192 -0
- package/src/emrtd/dg2.ts +111 -0
- package/src/emrtd/files.ts +215 -0
- package/src/emrtd/index.ts +30 -0
- package/src/emrtd/mrzKey.ts +54 -0
- package/src/emrtd/native.ts +79 -0
- package/src/emrtd/optionalRead.ts +42 -0
- package/src/emrtd/read.ts +224 -0
- package/src/emrtd/retry.ts +98 -0
- package/src/emrtd/secureMessaging.ts +178 -0
- package/src/emrtd/session.ts +200 -0
- package/src/emrtd/stages.ts +122 -0
- package/src/index.ts +110 -0
- package/src/lib/calendar.ts +71 -0
- package/src/lib/use-keyboard-inset.ts +34 -0
- package/src/liveness/challengeManager.ts +27 -1
- package/src/liveness/faceContinuity.ts +153 -0
- package/src/liveness/flashDetector.ts +145 -0
- package/src/liveness/flashReadyGate.ts +106 -0
- package/src/liveness/flashRunner.ts +118 -0
- package/src/liveness/integritySignals.ts +60 -0
- package/src/liveness/livenessState.ts +139 -0
- package/src/liveness/speech.ts +19 -1
- package/src/liveness/types.ts +48 -1
- package/src/liveness/useFlashPhase.ts +78 -0
- package/src/liveness/useLiveness.ts +260 -75
- package/src/liveness/visionCameraFaceDetector.ts +14 -0
- package/src/mrz/extract.ts +101 -0
- package/src/mrz/parse.ts +178 -0
- package/src/mrz/textRecognizer.ts +149 -0
- package/src/screens/ApplicantRoleStep.tsx +331 -0
- package/src/screens/BusinessDetailsStep.tsx +212 -0
- package/src/screens/BusinessDocumentSlot.tsx +167 -0
- package/src/screens/BusinessDocumentsStep.tsx +213 -0
- package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
- package/src/screens/BusinessKeyPersonRow.tsx +170 -0
- package/src/screens/CompanyInfoFields.tsx +83 -0
- package/src/screens/ConsentStep.tsx +68 -68
- package/src/screens/ContactChannelChoice.tsx +133 -0
- package/src/screens/ContactCodeStep.tsx +130 -0
- package/src/screens/ContactDestinationField.tsx +70 -0
- package/src/screens/ContactFooterNote.tsx +34 -0
- package/src/screens/ContactVerificationStep.tsx +199 -0
- package/src/screens/ContactVerifiedPanel.tsx +36 -0
- package/src/screens/CountrySelectStep.tsx +210 -0
- package/src/screens/DocumentCaptureStep.tsx +156 -132
- package/src/screens/IdInputStep.tsx +13 -5
- package/src/screens/IdTypeStep.tsx +74 -33
- package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
- package/src/screens/LivenessStep.tsx +155 -247
- package/src/screens/MrzScanView.tsx +173 -0
- package/src/screens/NfcStep.tsx +290 -0
- package/src/screens/ProofOfAddressStep.tsx +238 -0
- package/src/screens/QuestionnaireField.tsx +219 -0
- package/src/screens/QuestionnaireMoneyField.tsx +134 -0
- package/src/screens/QuestionnaireStep.tsx +76 -0
- package/src/screens/SubmittedStep.tsx +5 -0
- package/src/screens/consent/model.ts +109 -0
- package/src/screens/document/CameraPhase.tsx +150 -0
- package/src/screens/document/RequiredPill.tsx +60 -0
- package/src/screens/liveness/LightingBanner.tsx +50 -0
- package/src/screens/liveness/LivenessOutcome.tsx +109 -0
- package/src/screens/liveness/ProgressDots.tsx +67 -0
- package/src/screens/liveness/SelfiePreview.tsx +39 -0
- package/src/screens/liveness/constants.ts +31 -0
- package/src/screens/liveness/index.ts +8 -0
- package/src/screens/liveness/resolveGuidance.ts +27 -0
- package/src/screens/liveness/useSelfieUpload.ts +107 -0
- package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
- package/src/screens/nfc/NfcReadBody.tsx +63 -0
- package/src/screens/nfc/NfcReadProgress.tsx +96 -0
- package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
- package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
- package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
- package/src/screens/nfc/availability.ts +73 -0
- package/src/screens/nfc/readErrorMessage.ts +93 -0
- package/src/screens/nfc/useChipPortrait.ts +58 -0
- package/src/screens/nfc/useNfcAvailability.ts +82 -0
- package/src/screens/useFlashHole.ts +105 -0
- package/src/screens/useFlashSequence.ts +96 -0
- package/src/screens/usePoaAttach.ts +121 -0
- package/src/services/api-types.ts +164 -0
- package/src/services/api-verify-types.ts +99 -0
- package/src/services/api.ts +72 -102
- package/src/services/cardCrop.ts +25 -10
- package/src/services/contactErrors.ts +43 -0
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/documentPicker.ts +20 -0
- package/src/services/fingerprint-sources.ts +129 -0
- package/src/services/fingerprint.ts +101 -0
- package/src/services/haptics.ts +38 -0
- package/src/services/mediaCompress.ts +7 -2
- package/src/services/resolveUrl.ts +2 -2
- package/src/services/workflowGate.ts +138 -0
- package/src/specs/MyazaEmrtd.nitro.ts +114 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
- package/src/specs/MyazaRectDetector.nitro.ts +50 -0
- package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
- package/src/store/derive.ts +225 -0
- package/src/store/kycStore.ts +250 -149
- package/src/store/state.ts +253 -0
- package/src/store/submit.ts +101 -0
- package/src/store/submitApplicant.ts +81 -0
- package/src/types/appearance.ts +113 -0
- package/src/types/business.ts +131 -0
- package/src/types/config.ts +150 -124
- package/src/types/id-types.ts +57 -0
- package/src/types/verification.ts +5 -0
- package/src/types/workflow.ts +141 -0
- package/src/utils/amountFormat.ts +73 -0
- package/src/utils/tokens.ts +4 -2
- package/src/__tests__/cardCrop.test.ts +0 -39
- package/src/__tests__/deviceMetadata.test.ts +0 -34
- package/src/__tests__/errors.test.ts +0 -34
- package/src/__tests__/flow.test.ts +0 -61
- package/src/__tests__/gestureDetector.test.ts +0 -37
- package/src/__tests__/liveness.test.ts +0 -112
- package/src/__tests__/resolveUrl.test.ts +0 -64
- package/src/__tests__/validators.test.ts +0 -38
package/src/store/kycStore.ts
CHANGED
|
@@ -1,143 +1,56 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// kycStore — the
|
|
2
|
+
// kycStore — the flow store (mirrors Flutter's KYCNotifier + KYCState).
|
|
3
3
|
//
|
|
4
|
-
// One store
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// One store per modal instance via `createKycStore(config)`, so the flow state
|
|
5
|
+
// and its resolved API client are scoped to a single launch. The React layer
|
|
6
|
+
// exposes it through context + a `useStore` selector hook.
|
|
7
|
+
//
|
|
8
|
+
// The state SHAPES live in ./state.ts and the pure derivations in ./derive.ts
|
|
9
|
+
// (200-line rule); this file is the store factory and its actions. Both are
|
|
10
|
+
// re-exported below so existing importers are unaffected.
|
|
7
11
|
// ---------------------------------------------------------------------------
|
|
8
12
|
|
|
9
|
-
import { createStore
|
|
13
|
+
import { createStore } from 'zustand/vanilla';
|
|
14
|
+
|
|
15
|
+
import { createKYCApi } from '../services/api';
|
|
10
16
|
|
|
11
|
-
import { createKYCApi, type KYCApi, type VerifyRequest } from '../services/api';
|
|
12
17
|
import { resolveBaseUrl, normalizeDevAssetUrl } from '../services/resolveUrl';
|
|
13
18
|
import { withRetry } from '../services/retry';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import type { IdType, KYCStep, MyazaKYCConfig } from '../types/config';
|
|
19
|
+
import { collectFingerprint } from '../services/fingerprint';
|
|
20
|
+
import type { KYCStep, ResolvedKYCConfig } from '../types/config';
|
|
21
|
+
import { INITIAL_SERVER_CONFIG, describeConfigError, type ServerConfigState } from './serverConfig';
|
|
18
22
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
type
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
currentStep: KYCStep;
|
|
52
|
-
selectedIdType: IdType | null;
|
|
53
|
-
idNumber: string | null;
|
|
54
|
-
mediaIds: KYCMediaIds;
|
|
55
|
-
submissionResult: KYCSubmissionResult | null;
|
|
56
|
-
serverConfig: ServerConfigState;
|
|
57
|
-
documentScanPhase: DocumentScanPhase;
|
|
58
|
-
/** Sub-phase of the document-capture step — synced by the screen so the header
|
|
59
|
-
* title/description can be phase-aware (mirrors Flutter's docReviewPhase). */
|
|
60
|
-
documentCapturePhase: DocumentCapturePhase;
|
|
61
|
-
isLoading: boolean;
|
|
62
|
-
error: string | null;
|
|
63
|
-
|
|
64
|
-
// Actions
|
|
65
|
-
loadServerConfig: () => Promise<void>;
|
|
66
|
-
setIdType: (idType: IdType) => void;
|
|
67
|
-
setIdNumber: (idNumber: string) => void;
|
|
68
|
-
setMediaId: (key: MediaIdKey, mediaId: string) => void;
|
|
69
|
-
setDocumentMediaId: (mediaId: string, side: 'front' | 'back') => void;
|
|
70
|
-
setDocumentCapturePhase: (phase: DocumentCapturePhase) => void;
|
|
71
|
-
nextStep: () => void;
|
|
72
|
-
previousStep: () => void;
|
|
73
|
-
goToStep: (step: KYCStep) => void;
|
|
74
|
-
submitAsync: (onRetry?: (attempt: number, total: number) => void) => Promise<KYCSubmissionResult>;
|
|
75
|
-
reset: () => void;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export type KycStore = StoreApi<KycState>;
|
|
79
|
-
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
// Flow navigation — single source of truth for the 5-step sequence.
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
|
|
84
|
-
function livenessEnabled(state: KycState): boolean {
|
|
85
|
-
// Consumer baseline: liveness is on unless explicitly disabled.
|
|
86
|
-
if (state.config.enableLiveness === false) return false;
|
|
87
|
-
const idType = state.selectedIdType;
|
|
88
|
-
if (!idType) return true;
|
|
89
|
-
// Server flag wins when present; otherwise keep the consumer baseline (on).
|
|
90
|
-
const features = featuresFor(state.serverConfig, state.config.country, idType);
|
|
91
|
-
return features ? features.livenessCheck : true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** The step that follows `step`, given the current selection + flags. */
|
|
95
|
-
function nextStepAfter(step: KYCStep, state: KycState): KYCStep {
|
|
96
|
-
switch (step) {
|
|
97
|
-
case 'consent':
|
|
98
|
-
return 'id-type';
|
|
99
|
-
case 'id-type':
|
|
100
|
-
return state.selectedIdType && requiresDocumentCapture(state.selectedIdType)
|
|
101
|
-
? 'document-capture'
|
|
102
|
-
: 'id-input';
|
|
103
|
-
case 'document-capture':
|
|
104
|
-
case 'id-input':
|
|
105
|
-
return livenessEnabled(state) ? 'liveness' : 'submitted';
|
|
106
|
-
case 'liveness':
|
|
107
|
-
return 'submitted';
|
|
108
|
-
case 'submitted':
|
|
109
|
-
return 'submitted';
|
|
110
|
-
default:
|
|
111
|
-
return step;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** The step before `step` (for the back button). */
|
|
116
|
-
function previousStepBefore(step: KYCStep, state: KycState): KYCStep {
|
|
117
|
-
switch (step) {
|
|
118
|
-
case 'id-type':
|
|
119
|
-
return 'consent';
|
|
120
|
-
case 'document-capture':
|
|
121
|
-
case 'id-input':
|
|
122
|
-
return 'id-type';
|
|
123
|
-
case 'liveness':
|
|
124
|
-
return state.selectedIdType && requiresDocumentCapture(state.selectedIdType)
|
|
125
|
-
? 'document-capture'
|
|
126
|
-
: 'id-input';
|
|
127
|
-
case 'submitted':
|
|
128
|
-
// Submitted is terminal; back is a no-op in practice.
|
|
129
|
-
return livenessEnabled(state) ? 'liveness' : step;
|
|
130
|
-
case 'consent':
|
|
131
|
-
default:
|
|
132
|
-
return 'consent';
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
// Store factory
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
|
|
140
|
-
export function createKycStore(config: MyazaKYCConfig): KycStore {
|
|
23
|
+
EMPTY_BUSINESS,
|
|
24
|
+
EMPTY_BUSINESS_APPLICATION,
|
|
25
|
+
type KYCSubmissionResult,
|
|
26
|
+
type KycState,
|
|
27
|
+
type KycStore,
|
|
28
|
+
} from './state';
|
|
29
|
+
import { nextStepAfter, nfcDecision, previousStepBefore } from './derive';
|
|
30
|
+
import { buildVerifyRequest } from './submit';
|
|
31
|
+
import { applicantMediaCaptured, buildApplicantVerifyRequest } from './submitApplicant';
|
|
32
|
+
|
|
33
|
+
export * from './state';
|
|
34
|
+
export {
|
|
35
|
+
effectiveCountry,
|
|
36
|
+
livenessEnabled,
|
|
37
|
+
nfcEnabled,
|
|
38
|
+
nextStepAfter,
|
|
39
|
+
previousStepBefore,
|
|
40
|
+
stepOrderOptions,
|
|
41
|
+
stepProgress,
|
|
42
|
+
} from './derive';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `serverConfig` may be PRELOADED: resolving a workflow already returns the
|
|
46
|
+
* org's ID-type allowlist and branding, so seeding it here means the flow never
|
|
47
|
+
* makes the separate `/config` call — and never renders the loading state for
|
|
48
|
+
* data it already has.
|
|
49
|
+
*/
|
|
50
|
+
export function createKycStore(
|
|
51
|
+
config: ResolvedKYCConfig,
|
|
52
|
+
serverConfig?: ServerConfigState,
|
|
53
|
+
): KycStore {
|
|
141
54
|
const baseUrl = resolveBaseUrl(config.apiKey, config.devUrl);
|
|
142
55
|
const api = createKYCApi(baseUrl, config.apiKey);
|
|
143
56
|
|
|
@@ -151,13 +64,29 @@ export function createKycStore(config: MyazaKYCConfig): KycStore {
|
|
|
151
64
|
api,
|
|
152
65
|
|
|
153
66
|
currentStep: 'consent',
|
|
67
|
+
selectedCountry: null,
|
|
154
68
|
selectedIdType: null,
|
|
155
69
|
idNumber: null,
|
|
156
70
|
mediaIds: {},
|
|
157
71
|
submissionResult: null,
|
|
158
|
-
serverConfig: INITIAL_SERVER_CONFIG,
|
|
72
|
+
serverConfig: serverConfig ?? INITIAL_SERVER_CONFIG,
|
|
159
73
|
documentScanPhase: 'front',
|
|
160
74
|
documentCapturePhase: 'front',
|
|
75
|
+
immersiveCapture: false,
|
|
76
|
+
flashPaint: null,
|
|
77
|
+
navDirection: 'forward' as const,
|
|
78
|
+
questionnaireAnswers: {},
|
|
79
|
+
contact: {},
|
|
80
|
+
contactChallenge: null,
|
|
81
|
+
business: EMPTY_BUSINESS,
|
|
82
|
+
businessApplication: EMPTY_BUSINESS_APPLICATION,
|
|
83
|
+
applicantKeyPersonId: null,
|
|
84
|
+
keyPeopleInvites: [],
|
|
85
|
+
captureIntegrity: null,
|
|
86
|
+
mrzScan: null,
|
|
87
|
+
chipData: null,
|
|
88
|
+
poaDocumentType: null,
|
|
89
|
+
poaFileName: null,
|
|
161
90
|
isLoading: false,
|
|
162
91
|
error: null,
|
|
163
92
|
|
|
@@ -188,6 +117,17 @@ export function createKycStore(config: MyazaKYCConfig): KycStore {
|
|
|
188
117
|
}
|
|
189
118
|
},
|
|
190
119
|
|
|
120
|
+
setCountry(country) {
|
|
121
|
+
// Changing country invalidates the ID choice: the same key can mean a
|
|
122
|
+
// different document (or none) elsewhere, so it is cleared rather than
|
|
123
|
+
// silently carried across.
|
|
124
|
+
set((s) => ({
|
|
125
|
+
selectedCountry: country,
|
|
126
|
+
selectedIdType: s.selectedCountry === country ? s.selectedIdType : null,
|
|
127
|
+
idNumber: s.selectedCountry === country ? s.idNumber : null,
|
|
128
|
+
}));
|
|
129
|
+
},
|
|
130
|
+
|
|
191
131
|
setIdType(idType) {
|
|
192
132
|
set({ selectedIdType: idType });
|
|
193
133
|
},
|
|
@@ -210,14 +150,144 @@ export function createKycStore(config: MyazaKYCConfig): KycStore {
|
|
|
210
150
|
}));
|
|
211
151
|
},
|
|
212
152
|
|
|
153
|
+
setQuestionnaireAnswer(key, value) {
|
|
154
|
+
set((s) => {
|
|
155
|
+
const next = { ...s.questionnaireAnswers };
|
|
156
|
+
// An undefined answer is REMOVED rather than stored as undefined, so
|
|
157
|
+
// "unanswered" is one state and not two.
|
|
158
|
+
if (value === undefined) delete next[key];
|
|
159
|
+
else next[key] = value;
|
|
160
|
+
return { questionnaireAnswers: next };
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
setContactVerified(channel, destination, token) {
|
|
165
|
+
set((s) => ({
|
|
166
|
+
contact:
|
|
167
|
+
channel === 'email'
|
|
168
|
+
? { ...s.contact, emailAddress: destination, emailToken: token }
|
|
169
|
+
: { ...s.contact, phoneNumber: destination, phoneToken: token },
|
|
170
|
+
}));
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
setContactDestination(channel, destination) {
|
|
174
|
+
set((s) => ({
|
|
175
|
+
contact:
|
|
176
|
+
channel === 'email'
|
|
177
|
+
? { ...s.contact, emailAddress: destination }
|
|
178
|
+
: { ...s.contact, phoneNumber: destination },
|
|
179
|
+
}));
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
setBusinessField(key, value) {
|
|
183
|
+
set((s) => ({ business: { ...s.business, [key]: value } }));
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
setKeyPeople(rows) {
|
|
187
|
+
set((s) => ({ businessApplication: { ...s.businessApplication, keyPeople: rows } }));
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
setBusinessDocument(doc) {
|
|
191
|
+
set((s) => ({
|
|
192
|
+
businessApplication: {
|
|
193
|
+
...s.businessApplication,
|
|
194
|
+
// One upload per slot: re-uploading replaces rather than appends,
|
|
195
|
+
// so a user who retakes a photo does not submit both.
|
|
196
|
+
documents: [
|
|
197
|
+
...s.businessApplication.documents.filter((d) => d.type !== doc.type),
|
|
198
|
+
doc,
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
}));
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
removeBusinessDocument(type) {
|
|
205
|
+
set((s) => ({
|
|
206
|
+
businessApplication: {
|
|
207
|
+
...s.businessApplication,
|
|
208
|
+
documents: s.businessApplication.documents.filter((d) => d.type !== type),
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
setApplicant(role, name, keyPersonIndex = null) {
|
|
214
|
+
set((s) => ({
|
|
215
|
+
businessApplication: {
|
|
216
|
+
...s.businessApplication,
|
|
217
|
+
applicantRole: role,
|
|
218
|
+
applicantName: name,
|
|
219
|
+
applicantKeyPersonIndex: keyPersonIndex,
|
|
220
|
+
},
|
|
221
|
+
}));
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
setCaptureIntegrity(integrity) {
|
|
225
|
+
set({ captureIntegrity: integrity });
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
setMrzScan(scan) {
|
|
229
|
+
set({ mrzScan: scan });
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
setChipData(data) {
|
|
233
|
+
set({ chipData: data });
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
setProofOfAddress(mediaId, docType, fileName) {
|
|
237
|
+
set((s) => ({
|
|
238
|
+
mediaIds: { ...s.mediaIds, proofOfAddress: mediaId },
|
|
239
|
+
poaDocumentType: docType,
|
|
240
|
+
poaFileName: fileName,
|
|
241
|
+
}));
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
clearProofOfAddress() {
|
|
245
|
+
set((s) => {
|
|
246
|
+
const { proofOfAddress: _dropped, ...rest } = s.mediaIds;
|
|
247
|
+
return { mediaIds: rest, poaDocumentType: null, poaFileName: null };
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
|
|
213
251
|
setDocumentCapturePhase(phase) {
|
|
214
252
|
if (get().documentCapturePhase !== phase) set({ documentCapturePhase: phase });
|
|
215
253
|
},
|
|
216
254
|
|
|
255
|
+
setContactChallenge(challenge) {
|
|
256
|
+
const prev = get().contactChallenge;
|
|
257
|
+
// Guarded like the capture phase: the header re-renders off this, and a
|
|
258
|
+
// send that changes nothing should not churn it.
|
|
259
|
+
if (
|
|
260
|
+
prev?.channel === challenge?.channel &&
|
|
261
|
+
prev?.destination === challenge?.destination &&
|
|
262
|
+
prev?.via === challenge?.via
|
|
263
|
+
) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
set({ contactChallenge: challenge });
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
setFlashPaint(paint) {
|
|
270
|
+
set({ flashPaint: paint });
|
|
271
|
+
},
|
|
272
|
+
setImmersiveCapture(immersive) {
|
|
273
|
+
if (get().immersiveCapture !== immersive) set({ immersiveCapture: immersive });
|
|
274
|
+
},
|
|
275
|
+
|
|
217
276
|
nextStep() {
|
|
218
277
|
const next = nextStepAfter(get().currentStep, get());
|
|
278
|
+
// Leaving document capture is the moment the chip step either appears or
|
|
279
|
+
// silently does not. Four independent gates can remove it and a missing
|
|
280
|
+
// step looks the same however it went missing, so say which one.
|
|
281
|
+
if (__DEV__ && get().currentStep === 'document-capture' && next !== 'nfc') {
|
|
282
|
+
const d = nfcDecision(get());
|
|
283
|
+
if (!d.enabled) console.log('[myaza] NFC step skipped:', d.reason);
|
|
284
|
+
}
|
|
219
285
|
if (next !== get().currentStep) {
|
|
220
|
-
|
|
286
|
+
// Lowered on every step change: the flag belongs to a live camera, and
|
|
287
|
+
// leaving it raised means re-entering document-capture renders its
|
|
288
|
+
// primer chrome-free for a frame before the effect corrects it. The
|
|
289
|
+
// step that wants it raises it again on mount.
|
|
290
|
+
set({ currentStep: next, immersiveCapture: false, navDirection: 'forward' });
|
|
221
291
|
emitStepChange(next);
|
|
222
292
|
}
|
|
223
293
|
},
|
|
@@ -225,7 +295,7 @@ export function createKycStore(config: MyazaKYCConfig): KycStore {
|
|
|
225
295
|
previousStep() {
|
|
226
296
|
const prev = previousStepBefore(get().currentStep, get());
|
|
227
297
|
if (prev !== get().currentStep) {
|
|
228
|
-
set({ currentStep: prev });
|
|
298
|
+
set({ currentStep: prev, immersiveCapture: false, navDirection: 'back' });
|
|
229
299
|
emitStepChange(prev);
|
|
230
300
|
}
|
|
231
301
|
},
|
|
@@ -240,25 +310,42 @@ export function createKycStore(config: MyazaKYCConfig): KycStore {
|
|
|
240
310
|
async submitAsync(onRetry) {
|
|
241
311
|
set({ isLoading: true, error: null });
|
|
242
312
|
const state = get();
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
requestId: generateRequestId(),
|
|
251
|
-
device: collectDeviceMetadata() as unknown as Record<string, unknown>,
|
|
252
|
-
...(state.config.metadata ?? {}),
|
|
253
|
-
},
|
|
254
|
-
};
|
|
313
|
+
// Best-effort: a fingerprint that fails to collect is a missing signal,
|
|
314
|
+
// never a failed submission.
|
|
315
|
+
const fingerprint =
|
|
316
|
+
state.config.deviceIntelligence === false
|
|
317
|
+
? undefined
|
|
318
|
+
: await collectFingerprint().catch(() => undefined);
|
|
319
|
+
const request = buildVerifyRequest(state, fingerprint);
|
|
255
320
|
try {
|
|
256
321
|
const res = await withRetry(() => api.verify(request), { onRetry });
|
|
257
322
|
const result: KYCSubmissionResult = {
|
|
258
323
|
verificationId: res.verificationId,
|
|
259
324
|
status: 'pending',
|
|
260
325
|
};
|
|
261
|
-
set({
|
|
326
|
+
set({
|
|
327
|
+
submissionResult: result,
|
|
328
|
+
isLoading: false,
|
|
329
|
+
// Kept so the submitted screen can hand out the invite links. A
|
|
330
|
+
// retry of the same requestId returns these again, so they
|
|
331
|
+
// survive a re-submit.
|
|
332
|
+
...(res.applicantKeyPersonId ? { applicantKeyPersonId: res.applicantKeyPersonId } : {}),
|
|
333
|
+
...(res.keyPeopleInvites ? { keyPeopleInvites: res.keyPeopleInvites } : {}),
|
|
334
|
+
});
|
|
335
|
+
// Applicant KYC: fire-and-forget — the submitted screen shows after
|
|
336
|
+
// the BUSINESS submit; a failed applicant submit only warns (the org
|
|
337
|
+
// can re-invite the applicant from the dashboard). Mirrors the web
|
|
338
|
+
// and Flutter SDKs.
|
|
339
|
+
if (res.applicantKeyPersonId && applicantMediaCaptured(state)) {
|
|
340
|
+
void withRetry(() =>
|
|
341
|
+
api.verify(buildApplicantVerifyRequest(state, res.applicantKeyPersonId!, fingerprint)),
|
|
342
|
+
).catch((err) => {
|
|
343
|
+
console.warn(
|
|
344
|
+
'[MyazaKYC] Applicant identity submission failed — the organization can re-invite the applicant from the dashboard:',
|
|
345
|
+
err,
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
262
349
|
return result;
|
|
263
350
|
} catch (err) {
|
|
264
351
|
set({ isLoading: false, error: err instanceof Error ? err.message : 'Submission failed' });
|
|
@@ -269,12 +356,27 @@ export function createKycStore(config: MyazaKYCConfig): KycStore {
|
|
|
269
356
|
reset() {
|
|
270
357
|
set({
|
|
271
358
|
currentStep: 'consent',
|
|
359
|
+
selectedCountry: null,
|
|
272
360
|
selectedIdType: null,
|
|
273
361
|
idNumber: null,
|
|
274
362
|
mediaIds: {},
|
|
275
363
|
submissionResult: null,
|
|
276
364
|
documentScanPhase: 'front',
|
|
277
365
|
documentCapturePhase: 'front',
|
|
366
|
+
immersiveCapture: false,
|
|
367
|
+
flashPaint: null,
|
|
368
|
+
questionnaireAnswers: {},
|
|
369
|
+
contact: {},
|
|
370
|
+
contactChallenge: null,
|
|
371
|
+
business: EMPTY_BUSINESS,
|
|
372
|
+
businessApplication: EMPTY_BUSINESS_APPLICATION,
|
|
373
|
+
applicantKeyPersonId: null,
|
|
374
|
+
keyPeopleInvites: [],
|
|
375
|
+
captureIntegrity: null,
|
|
376
|
+
mrzScan: null,
|
|
377
|
+
chipData: null,
|
|
378
|
+
poaDocumentType: null,
|
|
379
|
+
poaFileName: null,
|
|
278
380
|
isLoading: false,
|
|
279
381
|
error: null,
|
|
280
382
|
});
|
|
@@ -285,4 +387,3 @@ export function createKycStore(config: MyazaKYCConfig): KycStore {
|
|
|
285
387
|
|
|
286
388
|
// Re-export the flow helpers for tests + screens that need to reason about
|
|
287
389
|
// navigation without mutating the store.
|
|
288
|
-
export { livenessEnabled, nextStepAfter, previousStepBefore };
|