@myazahq/kyc-sdk-react-native 2.0.1 → 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.
Files changed (283) hide show
  1. package/README.md +111 -8
  2. package/android/build.gradle +6 -0
  3. package/android/src/main/AndroidManifest.xml +14 -4
  4. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaEmrtd.kt +358 -0
  5. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +88 -1
  6. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaRectDetector.kt +34 -0
  7. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +118 -0
  8. package/android/src/main/java/com/margelo/nitro/myazakyc/Jp2Decoder.kt +117 -0
  9. package/app.plugin.js +102 -13
  10. package/ios/HybridMyazaEmrtd.swift +447 -0
  11. package/ios/HybridMyazaFaceDetector.swift +104 -2
  12. package/ios/HybridMyazaRectDetector.swift +103 -0
  13. package/ios/HybridMyazaTextRecognizer.swift +111 -0
  14. package/nitrogen/generated/.gitattributes +1 -0
  15. package/nitrogen/generated/android/KycSdkReactNative+autolinking.cmake +87 -0
  16. package/nitrogen/generated/android/KycSdkReactNative+autolinking.gradle +27 -0
  17. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.cpp +102 -0
  18. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.hpp +34 -0
  19. package/nitrogen/generated/android/c++/JDetectedRect.hpp +81 -0
  20. package/nitrogen/generated/android/c++/JEmrtdApduResponse.hpp +61 -0
  21. package/nitrogen/generated/android/c++/JEmrtdTagInfo.hpp +61 -0
  22. package/nitrogen/generated/android/c++/JFaceResult.hpp +113 -0
  23. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.cpp +176 -0
  24. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.hpp +75 -0
  25. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +60 -0
  26. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +63 -0
  27. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.cpp +60 -0
  28. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.hpp +63 -0
  29. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +80 -0
  30. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +64 -0
  31. package/nitrogen/generated/android/c++/JTextResult.hpp +76 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/DetectedRect.kt +81 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdApduResponse.kt +56 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdTagInfo.kt +56 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/FaceResult.kt +121 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaEmrtdSpec.kt +103 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +55 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaRectDetectorSpec.kt +55 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +60 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/KycSdkReactNativeOnLoad.kt +35 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/TextResult.kt +51 -0
  42. package/nitrogen/generated/ios/KycSdkReactNative+autolinking.rb +62 -0
  43. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +143 -0
  44. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +423 -0
  45. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Umbrella.hpp +81 -0
  46. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.mm +57 -0
  47. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.swift +62 -0
  48. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.cpp +11 -0
  49. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.hpp +184 -0
  50. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.cpp +11 -0
  51. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +87 -0
  52. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.cpp +11 -0
  53. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.hpp +87 -0
  54. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.cpp +11 -0
  55. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +98 -0
  56. package/nitrogen/generated/ios/swift/DetectedRect.swift +59 -0
  57. package/nitrogen/generated/ios/swift/EmrtdApduResponse.swift +34 -0
  58. package/nitrogen/generated/ios/swift/EmrtdTagInfo.swift +34 -0
  59. package/nitrogen/generated/ios/swift/FaceResult.swift +99 -0
  60. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  61. package/nitrogen/generated/ios/swift/Func_void_EmrtdApduResponse.swift +46 -0
  62. package/nitrogen/generated/ios/swift/Func_void_EmrtdTagInfo.swift +46 -0
  63. package/nitrogen/generated/ios/swift/Func_void_TextResult.swift +46 -0
  64. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  65. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  66. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec.swift +67 -0
  67. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec_cxx.swift +317 -0
  68. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +56 -0
  69. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +143 -0
  70. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec.swift +56 -0
  71. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec_cxx.swift +143 -0
  72. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +57 -0
  73. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +162 -0
  74. package/nitrogen/generated/ios/swift/TextResult.swift +35 -0
  75. package/nitrogen/generated/shared/c++/DetectedRect.hpp +107 -0
  76. package/nitrogen/generated/shared/c++/EmrtdApduResponse.hpp +87 -0
  77. package/nitrogen/generated/shared/c++/EmrtdTagInfo.hpp +87 -0
  78. package/nitrogen/generated/shared/c++/FaceResult.hpp +139 -0
  79. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.cpp +33 -0
  80. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.hpp +80 -0
  81. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +21 -0
  82. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +67 -0
  83. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.cpp +21 -0
  84. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.hpp +67 -0
  85. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +22 -0
  86. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +70 -0
  87. package/nitrogen/generated/shared/c++/TextResult.hpp +84 -0
  88. package/package.json +16 -4
  89. package/src/MyazaKYC.tsx +115 -22
  90. package/src/capture/documentIdentity.ts +153 -0
  91. package/src/capture/documentSignals.ts +177 -0
  92. package/src/capture/documentTextGate.ts +171 -0
  93. package/src/capture/hints.ts +57 -0
  94. package/src/capture/index.ts +24 -0
  95. package/src/capture/rectDetector.ts +84 -0
  96. package/src/capture/rectGate.ts +91 -0
  97. package/src/capture/useAutoCapture.ts +198 -0
  98. package/src/components/CameraPermissionView.tsx +39 -3
  99. package/src/components/CameraViewfinder.tsx +226 -70
  100. package/src/components/ContactCodeEntry.tsx +139 -0
  101. package/src/components/CountryField.tsx +87 -0
  102. package/src/components/CountryFlag.tsx +15 -15
  103. package/src/components/DialCodePicker.tsx +186 -0
  104. package/src/components/DocumentCropper.tsx +1 -1
  105. package/src/components/DocumentReview.tsx +142 -0
  106. package/src/components/DocumentReviewSide.tsx +135 -0
  107. package/src/components/DocumentReviewZoom.tsx +119 -0
  108. package/src/components/ExpiryCountdown.tsx +52 -0
  109. package/src/components/FlashOverlay.tsx +156 -0
  110. package/src/components/Icon.tsx +67 -1
  111. package/src/components/KycFlow.tsx +66 -94
  112. package/src/components/KycSheet.tsx +148 -18
  113. package/src/components/MediaSourceSheet.tsx +163 -0
  114. package/src/components/MyazaButton.tsx +14 -2
  115. package/src/components/MyazaDateField.tsx +240 -0
  116. package/src/components/MyazaInput.tsx +73 -26
  117. package/src/components/MyazaSelect.tsx +283 -0
  118. package/src/components/MyazaWordmark.tsx +33 -0
  119. package/src/components/OptionRow.tsx +132 -0
  120. package/src/components/PhoneNumberInput.tsx +149 -0
  121. package/src/components/PoweredBy.tsx +89 -0
  122. package/src/components/ReadyPrimer.tsx +149 -0
  123. package/src/components/StepView.tsx +91 -0
  124. package/src/components/Typography.tsx +18 -3
  125. package/src/components/VerifiedNotice.tsx +38 -0
  126. package/src/components/WhatsAppIcon.tsx +29 -0
  127. package/src/components/WorkflowGate.tsx +128 -0
  128. package/src/components/brand-font.ts +113 -0
  129. package/src/components/flashHoleGeometry.ts +35 -0
  130. package/src/components/flow/FatalConfigError.tsx +56 -0
  131. package/src/components/fonts.ts +2 -19
  132. package/src/components/readyPrimerContent.ts +52 -0
  133. package/src/components/runtime.tsx +38 -43
  134. package/src/components/stepHeaderMeta.tsx +87 -0
  135. package/src/components/theme-provider.tsx +89 -0
  136. package/src/components/useWorkflowMount.ts +116 -0
  137. package/src/components/viewfinder/CardGuide.tsx +60 -0
  138. package/src/components/viewfinder/DocumentGhost.tsx +149 -0
  139. package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
  140. package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
  141. package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
  142. package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
  143. package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
  144. package/src/config/brand.ts +97 -0
  145. package/src/config/business.ts +179 -0
  146. package/src/config/businessSteps.ts +105 -0
  147. package/src/config/contact.ts +159 -0
  148. package/src/config/countryNames.g.ts +254 -0
  149. package/src/config/currencyFlags.ts +144 -0
  150. package/src/config/font-resolve.ts +78 -0
  151. package/src/config/idTypes.ts +98 -1
  152. package/src/config/keyPeople.ts +152 -0
  153. package/src/config/phone.ts +39 -0
  154. package/src/config/proofOfAddress.ts +75 -0
  155. package/src/config/questionnaire.ts +153 -0
  156. package/src/config/regions.ts +118 -0
  157. package/src/config/registrationHint.ts +87 -0
  158. package/src/config/stepOrder.ts +118 -0
  159. package/src/config/theme.ts +38 -2
  160. package/src/config/workflowMerge.ts +124 -0
  161. package/src/emrtd/bac.ts +128 -0
  162. package/src/emrtd/bytes.ts +127 -0
  163. package/src/emrtd/crypto.ts +163 -0
  164. package/src/emrtd/der.ts +192 -0
  165. package/src/emrtd/dg2.ts +111 -0
  166. package/src/emrtd/files.ts +215 -0
  167. package/src/emrtd/index.ts +30 -0
  168. package/src/emrtd/mrzKey.ts +54 -0
  169. package/src/emrtd/native.ts +79 -0
  170. package/src/emrtd/optionalRead.ts +42 -0
  171. package/src/emrtd/read.ts +224 -0
  172. package/src/emrtd/retry.ts +98 -0
  173. package/src/emrtd/secureMessaging.ts +178 -0
  174. package/src/emrtd/session.ts +200 -0
  175. package/src/emrtd/stages.ts +122 -0
  176. package/src/index.ts +110 -0
  177. package/src/lib/calendar.ts +71 -0
  178. package/src/lib/use-keyboard-inset.ts +34 -0
  179. package/src/liveness/challengeManager.ts +27 -1
  180. package/src/liveness/faceContinuity.ts +153 -0
  181. package/src/liveness/flashDetector.ts +145 -0
  182. package/src/liveness/flashReadyGate.ts +106 -0
  183. package/src/liveness/flashRunner.ts +118 -0
  184. package/src/liveness/integritySignals.ts +60 -0
  185. package/src/liveness/livenessState.ts +139 -0
  186. package/src/liveness/speech.ts +19 -1
  187. package/src/liveness/types.ts +48 -1
  188. package/src/liveness/useFlashPhase.ts +78 -0
  189. package/src/liveness/useLiveness.ts +260 -75
  190. package/src/liveness/visionCameraFaceDetector.ts +14 -0
  191. package/src/mrz/extract.ts +101 -0
  192. package/src/mrz/parse.ts +178 -0
  193. package/src/mrz/textRecognizer.ts +149 -0
  194. package/src/screens/ApplicantRoleStep.tsx +331 -0
  195. package/src/screens/BusinessDetailsStep.tsx +212 -0
  196. package/src/screens/BusinessDocumentSlot.tsx +167 -0
  197. package/src/screens/BusinessDocumentsStep.tsx +213 -0
  198. package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
  199. package/src/screens/BusinessKeyPersonRow.tsx +170 -0
  200. package/src/screens/CompanyInfoFields.tsx +83 -0
  201. package/src/screens/ConsentStep.tsx +68 -68
  202. package/src/screens/ContactChannelChoice.tsx +133 -0
  203. package/src/screens/ContactCodeStep.tsx +130 -0
  204. package/src/screens/ContactDestinationField.tsx +70 -0
  205. package/src/screens/ContactFooterNote.tsx +34 -0
  206. package/src/screens/ContactVerificationStep.tsx +199 -0
  207. package/src/screens/ContactVerifiedPanel.tsx +36 -0
  208. package/src/screens/CountrySelectStep.tsx +210 -0
  209. package/src/screens/DocumentCaptureStep.tsx +190 -145
  210. package/src/screens/IdInputStep.tsx +19 -9
  211. package/src/screens/IdTypeStep.tsx +74 -33
  212. package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
  213. package/src/screens/LivenessStep.tsx +181 -267
  214. package/src/screens/MrzScanView.tsx +173 -0
  215. package/src/screens/NfcStep.tsx +290 -0
  216. package/src/screens/ProofOfAddressStep.tsx +238 -0
  217. package/src/screens/QuestionnaireField.tsx +219 -0
  218. package/src/screens/QuestionnaireMoneyField.tsx +134 -0
  219. package/src/screens/QuestionnaireStep.tsx +76 -0
  220. package/src/screens/SubmittedStep.tsx +5 -0
  221. package/src/screens/consent/model.ts +109 -0
  222. package/src/screens/document/CameraPhase.tsx +150 -0
  223. package/src/screens/document/RequiredPill.tsx +60 -0
  224. package/src/screens/liveness/LightingBanner.tsx +50 -0
  225. package/src/screens/liveness/LivenessOutcome.tsx +109 -0
  226. package/src/screens/liveness/ProgressDots.tsx +67 -0
  227. package/src/screens/liveness/SelfiePreview.tsx +39 -0
  228. package/src/screens/liveness/constants.ts +31 -0
  229. package/src/screens/liveness/index.ts +8 -0
  230. package/src/screens/liveness/resolveGuidance.ts +27 -0
  231. package/src/screens/liveness/useSelfieUpload.ts +107 -0
  232. package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
  233. package/src/screens/nfc/NfcReadBody.tsx +63 -0
  234. package/src/screens/nfc/NfcReadProgress.tsx +96 -0
  235. package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
  236. package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
  237. package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
  238. package/src/screens/nfc/availability.ts +73 -0
  239. package/src/screens/nfc/readErrorMessage.ts +93 -0
  240. package/src/screens/nfc/useChipPortrait.ts +58 -0
  241. package/src/screens/nfc/useNfcAvailability.ts +82 -0
  242. package/src/screens/useFlashHole.ts +105 -0
  243. package/src/screens/useFlashSequence.ts +96 -0
  244. package/src/screens/usePoaAttach.ts +121 -0
  245. package/src/services/api-types.ts +164 -0
  246. package/src/services/api-verify-types.ts +99 -0
  247. package/src/services/api.ts +72 -102
  248. package/src/services/cardCrop.ts +25 -10
  249. package/src/services/contactErrors.ts +43 -0
  250. package/src/services/deviceMetadata.ts +1 -1
  251. package/src/services/documentPicker.ts +20 -0
  252. package/src/services/fingerprint-sources.ts +129 -0
  253. package/src/services/fingerprint.ts +101 -0
  254. package/src/services/haptics.ts +38 -0
  255. package/src/services/mediaCompress.ts +7 -2
  256. package/src/services/resolveUrl.ts +3 -2
  257. package/src/services/validators.ts +3 -0
  258. package/src/services/workflowGate.ts +138 -0
  259. package/src/specs/MyazaEmrtd.nitro.ts +114 -0
  260. package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
  261. package/src/specs/MyazaRectDetector.nitro.ts +50 -0
  262. package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
  263. package/src/store/derive.ts +225 -0
  264. package/src/store/kycStore.ts +250 -149
  265. package/src/store/state.ts +253 -0
  266. package/src/store/submit.ts +101 -0
  267. package/src/store/submitApplicant.ts +81 -0
  268. package/src/types/appearance.ts +113 -0
  269. package/src/types/business.ts +131 -0
  270. package/src/types/config.ts +150 -119
  271. package/src/types/id-types.ts +57 -0
  272. package/src/types/verification.ts +5 -0
  273. package/src/types/workflow.ts +141 -0
  274. package/src/utils/amountFormat.ts +73 -0
  275. package/src/utils/tokens.ts +4 -2
  276. package/src/__tests__/cardCrop.test.ts +0 -39
  277. package/src/__tests__/deviceMetadata.test.ts +0 -34
  278. package/src/__tests__/errors.test.ts +0 -34
  279. package/src/__tests__/flow.test.ts +0 -61
  280. package/src/__tests__/gestureDetector.test.ts +0 -37
  281. package/src/__tests__/liveness.test.ts +0 -112
  282. package/src/__tests__/resolveUrl.test.ts +0 -64
  283. package/src/__tests__/validators.test.ts +0 -38
@@ -1,143 +1,56 @@
1
1
  // ---------------------------------------------------------------------------
2
- // kycStore — the main flow/state store (mirrors Flutter's KYCNotifier + KYCState).
2
+ // kycStore — the flow store (mirrors Flutter's KYCNotifier + KYCState).
3
3
  //
4
- // One store is created per modal instance via `createKycStore(config)` so the
5
- // flow state (and the resolved API client) is scoped to a single launch. The
6
- // React layer (Step 2) exposes it through context + a `useStore` selector hook.
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, type StoreApi } from 'zustand/vanilla';
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 { collectDeviceMetadata } from '../services/deviceMetadata';
15
- import { requiresDocumentCapture } from '../config/idTypes';
16
- import { generateRequestId } from '../utils/uuid';
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
- INITIAL_SERVER_CONFIG,
20
- describeConfigError,
21
- featuresFor,
22
- type ServerConfigState,
23
- } from './serverConfig';
24
-
25
- export interface KYCMediaIds {
26
- documentFront?: string;
27
- documentBack?: string;
28
- selfie?: string;
29
- documentFrontVideo?: string;
30
- documentBackVideo?: string;
31
- livenessVideo?: string;
32
- }
33
-
34
- export interface KYCSubmissionResult {
35
- verificationId: string;
36
- status: 'pending';
37
- }
38
-
39
- export type DocumentScanPhase = 'front' | 'back' | 'complete';
40
-
41
- /** Document-capture sub-phase — drives the sheet header title/description. */
42
- export type DocumentCapturePhase = 'front' | 'front-preview' | 'back' | 'review';
43
-
44
- /** The mediaIds keys settable via `setMediaId`. */
45
- export type MediaIdKey = keyof KYCMediaIds;
46
-
47
- export interface KycState {
48
- config: MyazaKYCConfig;
49
- api: KYCApi;
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
- set({ currentStep: next });
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
- const request: VerifyRequest = {
244
- country: state.config.country,
245
- idType: state.selectedIdType ?? '',
246
- idNumber: state.idNumber ?? undefined,
247
- userData: state.config.userData,
248
- mediaIds: state.mediaIds,
249
- metadata: {
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({ submissionResult: result, isLoading: false });
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 };