@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.
Files changed (281) 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/CameraViewfinder.tsx +226 -70
  99. package/src/components/ContactCodeEntry.tsx +139 -0
  100. package/src/components/CountryField.tsx +87 -0
  101. package/src/components/CountryFlag.tsx +15 -15
  102. package/src/components/DialCodePicker.tsx +186 -0
  103. package/src/components/DocumentCropper.tsx +1 -1
  104. package/src/components/DocumentReview.tsx +142 -0
  105. package/src/components/DocumentReviewSide.tsx +135 -0
  106. package/src/components/DocumentReviewZoom.tsx +119 -0
  107. package/src/components/ExpiryCountdown.tsx +52 -0
  108. package/src/components/FlashOverlay.tsx +156 -0
  109. package/src/components/Icon.tsx +67 -1
  110. package/src/components/KycFlow.tsx +66 -94
  111. package/src/components/KycSheet.tsx +148 -18
  112. package/src/components/MediaSourceSheet.tsx +163 -0
  113. package/src/components/MyazaButton.tsx +14 -2
  114. package/src/components/MyazaDateField.tsx +240 -0
  115. package/src/components/MyazaInput.tsx +73 -26
  116. package/src/components/MyazaSelect.tsx +283 -0
  117. package/src/components/MyazaWordmark.tsx +33 -0
  118. package/src/components/OptionRow.tsx +132 -0
  119. package/src/components/PhoneNumberInput.tsx +149 -0
  120. package/src/components/PoweredBy.tsx +89 -0
  121. package/src/components/ReadyPrimer.tsx +149 -0
  122. package/src/components/StepView.tsx +91 -0
  123. package/src/components/Typography.tsx +18 -3
  124. package/src/components/VerifiedNotice.tsx +38 -0
  125. package/src/components/WhatsAppIcon.tsx +29 -0
  126. package/src/components/WorkflowGate.tsx +128 -0
  127. package/src/components/brand-font.ts +113 -0
  128. package/src/components/flashHoleGeometry.ts +35 -0
  129. package/src/components/flow/FatalConfigError.tsx +56 -0
  130. package/src/components/fonts.ts +2 -19
  131. package/src/components/readyPrimerContent.ts +52 -0
  132. package/src/components/runtime.tsx +38 -43
  133. package/src/components/stepHeaderMeta.tsx +87 -0
  134. package/src/components/theme-provider.tsx +89 -0
  135. package/src/components/useWorkflowMount.ts +116 -0
  136. package/src/components/viewfinder/CardGuide.tsx +60 -0
  137. package/src/components/viewfinder/DocumentGhost.tsx +149 -0
  138. package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
  139. package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
  140. package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
  141. package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
  142. package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
  143. package/src/config/brand.ts +97 -0
  144. package/src/config/business.ts +179 -0
  145. package/src/config/businessSteps.ts +105 -0
  146. package/src/config/contact.ts +159 -0
  147. package/src/config/countryNames.g.ts +254 -0
  148. package/src/config/currencyFlags.ts +144 -0
  149. package/src/config/font-resolve.ts +78 -0
  150. package/src/config/idTypes.ts +96 -1
  151. package/src/config/keyPeople.ts +152 -0
  152. package/src/config/phone.ts +39 -0
  153. package/src/config/proofOfAddress.ts +75 -0
  154. package/src/config/questionnaire.ts +153 -0
  155. package/src/config/regions.ts +118 -0
  156. package/src/config/registrationHint.ts +87 -0
  157. package/src/config/stepOrder.ts +118 -0
  158. package/src/config/theme.ts +38 -2
  159. package/src/config/workflowMerge.ts +124 -0
  160. package/src/emrtd/bac.ts +128 -0
  161. package/src/emrtd/bytes.ts +127 -0
  162. package/src/emrtd/crypto.ts +163 -0
  163. package/src/emrtd/der.ts +192 -0
  164. package/src/emrtd/dg2.ts +111 -0
  165. package/src/emrtd/files.ts +215 -0
  166. package/src/emrtd/index.ts +30 -0
  167. package/src/emrtd/mrzKey.ts +54 -0
  168. package/src/emrtd/native.ts +79 -0
  169. package/src/emrtd/optionalRead.ts +42 -0
  170. package/src/emrtd/read.ts +224 -0
  171. package/src/emrtd/retry.ts +98 -0
  172. package/src/emrtd/secureMessaging.ts +178 -0
  173. package/src/emrtd/session.ts +200 -0
  174. package/src/emrtd/stages.ts +122 -0
  175. package/src/index.ts +110 -0
  176. package/src/lib/calendar.ts +71 -0
  177. package/src/lib/use-keyboard-inset.ts +34 -0
  178. package/src/liveness/challengeManager.ts +27 -1
  179. package/src/liveness/faceContinuity.ts +153 -0
  180. package/src/liveness/flashDetector.ts +145 -0
  181. package/src/liveness/flashReadyGate.ts +106 -0
  182. package/src/liveness/flashRunner.ts +118 -0
  183. package/src/liveness/integritySignals.ts +60 -0
  184. package/src/liveness/livenessState.ts +139 -0
  185. package/src/liveness/speech.ts +19 -1
  186. package/src/liveness/types.ts +48 -1
  187. package/src/liveness/useFlashPhase.ts +78 -0
  188. package/src/liveness/useLiveness.ts +260 -75
  189. package/src/liveness/visionCameraFaceDetector.ts +14 -0
  190. package/src/mrz/extract.ts +101 -0
  191. package/src/mrz/parse.ts +178 -0
  192. package/src/mrz/textRecognizer.ts +149 -0
  193. package/src/screens/ApplicantRoleStep.tsx +331 -0
  194. package/src/screens/BusinessDetailsStep.tsx +212 -0
  195. package/src/screens/BusinessDocumentSlot.tsx +167 -0
  196. package/src/screens/BusinessDocumentsStep.tsx +213 -0
  197. package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
  198. package/src/screens/BusinessKeyPersonRow.tsx +170 -0
  199. package/src/screens/CompanyInfoFields.tsx +83 -0
  200. package/src/screens/ConsentStep.tsx +68 -68
  201. package/src/screens/ContactChannelChoice.tsx +133 -0
  202. package/src/screens/ContactCodeStep.tsx +130 -0
  203. package/src/screens/ContactDestinationField.tsx +70 -0
  204. package/src/screens/ContactFooterNote.tsx +34 -0
  205. package/src/screens/ContactVerificationStep.tsx +199 -0
  206. package/src/screens/ContactVerifiedPanel.tsx +36 -0
  207. package/src/screens/CountrySelectStep.tsx +210 -0
  208. package/src/screens/DocumentCaptureStep.tsx +156 -132
  209. package/src/screens/IdInputStep.tsx +13 -5
  210. package/src/screens/IdTypeStep.tsx +74 -33
  211. package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
  212. package/src/screens/LivenessStep.tsx +155 -247
  213. package/src/screens/MrzScanView.tsx +173 -0
  214. package/src/screens/NfcStep.tsx +290 -0
  215. package/src/screens/ProofOfAddressStep.tsx +238 -0
  216. package/src/screens/QuestionnaireField.tsx +219 -0
  217. package/src/screens/QuestionnaireMoneyField.tsx +134 -0
  218. package/src/screens/QuestionnaireStep.tsx +76 -0
  219. package/src/screens/SubmittedStep.tsx +5 -0
  220. package/src/screens/consent/model.ts +109 -0
  221. package/src/screens/document/CameraPhase.tsx +150 -0
  222. package/src/screens/document/RequiredPill.tsx +60 -0
  223. package/src/screens/liveness/LightingBanner.tsx +50 -0
  224. package/src/screens/liveness/LivenessOutcome.tsx +109 -0
  225. package/src/screens/liveness/ProgressDots.tsx +67 -0
  226. package/src/screens/liveness/SelfiePreview.tsx +39 -0
  227. package/src/screens/liveness/constants.ts +31 -0
  228. package/src/screens/liveness/index.ts +8 -0
  229. package/src/screens/liveness/resolveGuidance.ts +27 -0
  230. package/src/screens/liveness/useSelfieUpload.ts +107 -0
  231. package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
  232. package/src/screens/nfc/NfcReadBody.tsx +63 -0
  233. package/src/screens/nfc/NfcReadProgress.tsx +96 -0
  234. package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
  235. package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
  236. package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
  237. package/src/screens/nfc/availability.ts +73 -0
  238. package/src/screens/nfc/readErrorMessage.ts +93 -0
  239. package/src/screens/nfc/useChipPortrait.ts +58 -0
  240. package/src/screens/nfc/useNfcAvailability.ts +82 -0
  241. package/src/screens/useFlashHole.ts +105 -0
  242. package/src/screens/useFlashSequence.ts +96 -0
  243. package/src/screens/usePoaAttach.ts +121 -0
  244. package/src/services/api-types.ts +164 -0
  245. package/src/services/api-verify-types.ts +99 -0
  246. package/src/services/api.ts +72 -102
  247. package/src/services/cardCrop.ts +25 -10
  248. package/src/services/contactErrors.ts +43 -0
  249. package/src/services/deviceMetadata.ts +1 -1
  250. package/src/services/documentPicker.ts +20 -0
  251. package/src/services/fingerprint-sources.ts +129 -0
  252. package/src/services/fingerprint.ts +101 -0
  253. package/src/services/haptics.ts +38 -0
  254. package/src/services/mediaCompress.ts +7 -2
  255. package/src/services/resolveUrl.ts +2 -2
  256. package/src/services/workflowGate.ts +138 -0
  257. package/src/specs/MyazaEmrtd.nitro.ts +114 -0
  258. package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
  259. package/src/specs/MyazaRectDetector.nitro.ts +50 -0
  260. package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
  261. package/src/store/derive.ts +225 -0
  262. package/src/store/kycStore.ts +250 -149
  263. package/src/store/state.ts +253 -0
  264. package/src/store/submit.ts +101 -0
  265. package/src/store/submitApplicant.ts +81 -0
  266. package/src/types/appearance.ts +113 -0
  267. package/src/types/business.ts +131 -0
  268. package/src/types/config.ts +150 -124
  269. package/src/types/id-types.ts +57 -0
  270. package/src/types/verification.ts +5 -0
  271. package/src/types/workflow.ts +141 -0
  272. package/src/utils/amountFormat.ts +73 -0
  273. package/src/utils/tokens.ts +4 -2
  274. package/src/__tests__/cardCrop.test.ts +0 -39
  275. package/src/__tests__/deviceMetadata.test.ts +0 -34
  276. package/src/__tests__/errors.test.ts +0 -34
  277. package/src/__tests__/flow.test.ts +0 -61
  278. package/src/__tests__/gestureDetector.test.ts +0 -37
  279. package/src/__tests__/liveness.test.ts +0 -112
  280. package/src/__tests__/resolveUrl.test.ts +0 -64
  281. package/src/__tests__/validators.test.ts +0 -38
@@ -0,0 +1,253 @@
1
+ import type { FlashHole } from '../components/flashHoleGeometry';
2
+ // ---------------------------------------------------------------------------
3
+ // The flow's state shapes.
4
+ //
5
+ // Split from kycStore.ts (200-line rule). These are pure type declarations plus
6
+ // their empty values — no store, no actions — so any module can describe the
7
+ // state without importing the store and its whole dependency graph.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ import type { StoreApi } from 'zustand/vanilla';
11
+
12
+ import type { KYCApi, KeyPersonInvite } from '../services/api';
13
+ import type { IdType, KYCStep, ResolvedKYCConfig } from '../types/config';
14
+ import type { PoaDocumentType } from '../types/workflow';
15
+ import type { ApplicantRole } from '../types/business';
16
+ import type { QuestionnaireAnswers } from '../config/questionnaire';
17
+ import type { ContactChallenge } from '../config/contact';
18
+ import type { QuestionnaireAnswerValue } from '../types/workflow';
19
+ import type { KeyPersonEntry } from '../config/keyPeople';
20
+ import type { ServerConfigState } from './serverConfig';
21
+ import type { CaptureIntegrity } from '../liveness/integritySignals';
22
+ import type { MrzScan } from '../mrz/parse';
23
+ import type { EmrtdReadResult } from '../emrtd';
24
+
25
+ export interface KYCMediaIds {
26
+ /** Proof-of-address document (image or PDF). */
27
+ proofOfAddress?: string;
28
+ documentFront?: string;
29
+ documentBack?: string;
30
+ selfie?: string;
31
+ documentFrontVideo?: string;
32
+ documentBackVideo?: string;
33
+ livenessVideo?: string;
34
+ }
35
+
36
+ export interface KYCSubmissionResult {
37
+ verificationId: string;
38
+ status: 'pending';
39
+ }
40
+
41
+ export type DocumentScanPhase = 'front' | 'back' | 'complete';
42
+
43
+ /** What the business-details step (and the KYB application steps) collect. */
44
+ export interface BusinessState {
45
+ /** Picked registry country — null until chosen on a multi-registry flow. */
46
+ country: string | null;
47
+ /** Picked product key — null until chosen / defaulted. */
48
+ product: string | null;
49
+ registrationNumber: string;
50
+ registrationName: string;
51
+ /** Where key-people invite links are emailed. */
52
+ contactEmail: string;
53
+ address: string;
54
+ email: string;
55
+ phone: string;
56
+ website: string;
57
+ }
58
+
59
+ /** One uploaded supporting document. */
60
+ export interface BusinessDocumentUpload {
61
+ type: string;
62
+ mediaId: string;
63
+ fileName: string;
64
+ /**
65
+ * Local URI of the picked image, for the slot thumbnail. Kept on the RECORD
66
+ * (not screen state) so the preview survives leaving and re-entering the
67
+ * step. Never submitted.
68
+ */
69
+ previewUri?: string;
70
+ isPdf?: boolean;
71
+ }
72
+
73
+ /** The KYB application beyond the registry details. */
74
+ export interface BusinessApplicationState {
75
+ keyPeople: KeyPersonEntry[];
76
+ documents: BusinessDocumentUpload[];
77
+ applicantRole: ApplicantRole | null;
78
+ applicantName: string;
79
+ /**
80
+ * The applicant picked THEMSELVES from the entered key people (index into
81
+ * `keyPeople`). Null = they're someone else / nothing picked. The flagged
82
+ * entry is merged server-side with the applicant row — one person, one KYC,
83
+ * one screening, no duplicate invite.
84
+ */
85
+ applicantKeyPersonIndex: number | null;
86
+ }
87
+
88
+ export const EMPTY_BUSINESS_APPLICATION: BusinessApplicationState = {
89
+ keyPeople: [],
90
+ documents: [],
91
+ applicantRole: null,
92
+ applicantName: '',
93
+ applicantKeyPersonIndex: null,
94
+ };
95
+
96
+ export const EMPTY_BUSINESS: BusinessState = {
97
+ country: null,
98
+ product: null,
99
+ registrationNumber: '',
100
+ registrationName: '',
101
+ contactEmail: '',
102
+ address: '',
103
+ email: '',
104
+ phone: '',
105
+ website: '',
106
+ };
107
+
108
+ export interface ContactState {
109
+ emailAddress?: string;
110
+ phoneNumber?: string;
111
+ /** Single-use proof from a passed email check. */
112
+ emailToken?: string;
113
+ /** Single-use proof from a passed phone check. */
114
+ phoneToken?: string;
115
+ }
116
+
117
+ /** Document-capture sub-phase — drives the sheet header title/description. */
118
+ export type DocumentCapturePhase = 'front' | 'front-preview' | 'back' | 'review';
119
+
120
+ /** The mediaIds keys settable via `setMediaId`. */
121
+ export type MediaIdKey = keyof KYCMediaIds;
122
+
123
+ export interface KycState {
124
+ config: ResolvedKYCConfig;
125
+ api: KYCApi;
126
+
127
+ currentStep: KYCStep;
128
+ /**
129
+ * The country picked on the multi-region country-select step. Null on a
130
+ * single-country flow, where `config.country` is the answer.
131
+ */
132
+ selectedCountry: string | null;
133
+ selectedIdType: IdType | null;
134
+ idNumber: string | null;
135
+ mediaIds: KYCMediaIds;
136
+ submissionResult: KYCSubmissionResult | null;
137
+ serverConfig: ServerConfigState;
138
+ documentScanPhase: DocumentScanPhase;
139
+ /** Sub-phase of the document-capture step — synced by the screen so the header
140
+ * title/description can be phase-aware (mirrors Flutter's docReviewPhase). */
141
+ documentCapturePhase: DocumentCapturePhase;
142
+ /**
143
+ * True while a full-bleed camera owns the screen.
144
+ *
145
+ * The sheet's header, padding and scroll view are what force a small
146
+ * viewfinder on a short phone — and a camera you have to SCROLL to is a
147
+ * broken camera. When this is set the shell steps out of the way entirely.
148
+ */
149
+ immersiveCapture: boolean;
150
+ /**
151
+ * What the flash overlay should paint, hoisted OUT of the liveness step.
152
+ *
153
+ * It has to be drawn at the sheet root, not inside the step: the step sits in
154
+ * a padded, scrolling body, so an overlay there lights only a fraction of the
155
+ * display — and the screen IS the light source for this check.
156
+ *
157
+ * It cannot be drawn in a modal of its own either. iOS scales a presenting
158
+ * sheet back when a modal appears over it, and RN's `measureInWindow` reports
159
+ * Yoga LAYOUT coordinates, which never reflect that UIKit transform. The
160
+ * cutout would be measured at full size while the preview renders at ~0.77 of
161
+ * it — off by the transform, with no reading that can ever converge. Painting
162
+ * in the same tree means the hole and the preview scale together, so the two
163
+ * agree by construction rather than by measurement.
164
+ */
165
+ flashPaint: { color: string | null; hole: FlashHole | null } | null;
166
+ /**
167
+ * Which way the user last moved through the flow.
168
+ *
169
+ * A step that SKIPS ITSELF (no NFC radio, a disabled feature) must skip the
170
+ * way the user was already going. Always advancing forward makes such a step
171
+ * a one-way valve: pressing Back onto it bounces you straight forward again,
172
+ * so the steps before it become unreachable.
173
+ */
174
+ navDirection: 'forward' | 'back';
175
+ /** Answers to the workflow's compliance questionnaire, keyed by field key. */
176
+ questionnaireAnswers: QuestionnaireAnswers;
177
+ /** Business (KYB) registry details. Unused on an individual flow. */
178
+ business: BusinessState;
179
+ /** The rest of the KYB application: people, documents, applicant role. */
180
+ businessApplication: BusinessApplicationState;
181
+ /**
182
+ * Set by a KYB submission that requires applicant verification — the
183
+ * KeyPerson id the applicant's own individual check links back to.
184
+ */
185
+ applicantKeyPersonId: string | null;
186
+ /** Per-person hosted-KYC links returned by a KYB submission. */
187
+ keyPeopleInvites: KeyPersonInvite[];
188
+ /**
189
+ * What the client observed while capturing — the flash claim in particular.
190
+ * Sent as CONTEXT, never as a verdict: the server re-analyses the recorded
191
+ * video against the sequence claimed here.
192
+ */
193
+ captureIntegrity: CaptureIntegrity | null;
194
+ /**
195
+ * The MRZ read off the document photo. It is the KEY that unlocks the chip —
196
+ * possession of the document is the access control — and it is also what the
197
+ * server checks the chip's own DG1 against.
198
+ */
199
+ mrzScan: MrzScan | null;
200
+ /** What the chip returned. Absent when it was skipped or unreadable. */
201
+ chipData: EmrtdReadResult | null;
202
+ /** Which kind of document was uploaded as proof of address. */
203
+ poaDocumentType: PoaDocumentType | null;
204
+ /** Its file name, so the uploaded state can name what it has. */
205
+ poaFileName: string | null;
206
+ /**
207
+ * Contact-verification results. The proofs are single-use and ride the
208
+ * /verify submission; the addresses are kept so returning to the step (or a
209
+ * retry) shows what the user already typed rather than a blank field.
210
+ */
211
+ contact: ContactState;
212
+ /** Outstanding contact code, for the header. Null while entering a destination. */
213
+ contactChallenge: ContactChallenge | null;
214
+ isLoading: boolean;
215
+ error: string | null;
216
+
217
+ // Actions
218
+ loadServerConfig: () => Promise<void>;
219
+ setCountry: (country: string) => void;
220
+ setIdType: (idType: IdType) => void;
221
+ setIdNumber: (idNumber: string) => void;
222
+ setMediaId: (key: MediaIdKey, mediaId: string) => void;
223
+ setDocumentMediaId: (mediaId: string, side: 'front' | 'back') => void;
224
+ setQuestionnaireAnswer: (key: string, value: QuestionnaireAnswerValue | undefined) => void;
225
+ setContactVerified: (channel: 'email' | 'phone', destination: string, token: string) => void;
226
+ setContactDestination: (channel: 'email' | 'phone', destination: string) => void;
227
+ setBusinessField: <K extends keyof BusinessState>(key: K, value: BusinessState[K]) => void;
228
+ setKeyPeople: (rows: KeyPersonEntry[]) => void;
229
+ setBusinessDocument: (doc: BusinessDocumentUpload) => void;
230
+ removeBusinessDocument: (type: string) => void;
231
+ setApplicant: (role: ApplicantRole, name: string, keyPersonIndex?: number | null) => void;
232
+ setCaptureIntegrity: (integrity: CaptureIntegrity) => void;
233
+ setMrzScan: (scan: MrzScan) => void;
234
+ setChipData: (data: EmrtdReadResult) => void;
235
+ setProofOfAddress: (mediaId: string, docType: PoaDocumentType, fileName: string) => void;
236
+ clearProofOfAddress: () => void;
237
+ setDocumentCapturePhase: (phase: DocumentCapturePhase) => void;
238
+ setContactChallenge: (challenge: ContactChallenge | null) => void;
239
+ setImmersiveCapture: (immersive: boolean) => void;
240
+ setFlashPaint: (paint: { color: string | null; hole: FlashHole | null } | null) => void;
241
+ nextStep: () => void;
242
+ previousStep: () => void;
243
+ goToStep: (step: KYCStep) => void;
244
+ submitAsync: (onRetry?: (attempt: number, total: number) => void) => Promise<KYCSubmissionResult>;
245
+ reset: () => void;
246
+ }
247
+
248
+
249
+ // ---------------------------------------------------------------------------
250
+ // Flow navigation — single source of truth for the 5-step sequence.
251
+ // ---------------------------------------------------------------------------
252
+
253
+ export type KycStore = StoreApi<KycState>;
@@ -0,0 +1,101 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Building the /verify request.
3
+ //
4
+ // Split from kycStore.ts (200-line rule). Pure: state in, request out — so what
5
+ // the SDK actually sends can be reasoned about (and tested) without a store, a
6
+ // network, or a running flow.
7
+ //
8
+ // Two rules shape it. A KYB submission carries registry details instead of an
9
+ // ID, so the country source, the `idType` slot and the media all differ. And
10
+ // every optional block is sent ONLY when the flow collected it: an inactive
11
+ // questionnaire must not put an empty object on the verification record, and an
12
+ // unconfigured contact proof must not claim a check that never ran.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ import { collectDeviceMetadata } from '../services/deviceMetadata';
16
+ import { generateRequestId } from '../utils/uuid';
17
+ import { hasActiveQuestionnaire, questionnairePayload } from '../config/questionnaire';
18
+ import { isBusinessFlow } from '../config/business';
19
+ import { businessSubmission, effectiveCountry } from './derive';
20
+ import type { ClientFingerprint } from '../services/fingerprint';
21
+ import type { VerifyRequest } from '../services/api';
22
+ import type { KycState } from './state';
23
+
24
+ export function buildVerifyRequest(
25
+ state: KycState,
26
+ fingerprint: ClientFingerprint | undefined,
27
+ ): VerifyRequest {
28
+ // A KYB flow submits registry details instead of an ID: a different country
29
+ // source (the picked registry), the product key riding on `idType`, and no
30
+ // capture media at all.
31
+ const business = isBusinessFlow(state.config) ? businessSubmission(state) : null;
32
+
33
+ return {
34
+ country: business ? business.country : effectiveCountry(state),
35
+ idType: business ? business.product : (state.selectedIdType ?? ''),
36
+ idNumber: business ? undefined : (state.idNumber ?? undefined),
37
+ ...(business ? { business: business.payload } : {}),
38
+ // The org's user reference (becomes Entity.externalUserId at the KYC seam).
39
+ ...(state.config.userId ? { userId: state.config.userId } : {}),
40
+ userData: business ? undefined : state.config.userData,
41
+ mediaIds: business ? {} : state.mediaIds,
42
+ ...(state.config.workflowId ? { workflowId: state.config.workflowId } : {}),
43
+ ...(state.poaDocumentType ? { proofOfAddressType: state.poaDocumentType } : {}),
44
+ ...(state.contact.emailToken || state.contact.phoneToken
45
+ ? {
46
+ contact: {
47
+ ...(state.contact.emailToken ? { emailToken: state.contact.emailToken } : {}),
48
+ ...(state.contact.phoneToken ? { phoneToken: state.contact.phoneToken } : {}),
49
+ },
50
+ }
51
+ : {}),
52
+ // The chip read, when one succeeded.
53
+ //
54
+ // This was the whole point of the NFC step and it was never sent: the read
55
+ // completed, `setChipData` stored the result, and nothing ever put it in the
56
+ // request. Every field the server wants was sitting in the store while the
57
+ // verification recorded no chip at all — passive authentication never ran,
58
+ // the assurance level never reached `chip`, and the dashboard showed nothing.
59
+ //
60
+ // Omitted entirely when absent rather than sent empty: the server treats a
61
+ // present-but-hollow block as a failed read, which is not the same as a
62
+ // document whose chip was never scanned.
63
+ ...(state.chipData
64
+ ? {
65
+ nfc: {
66
+ dg1: state.chipData.dg1,
67
+ ...(state.chipData.sod ? { sod: state.chipData.sod } : {}),
68
+ ...(state.chipData.dg2 ? { dg2: state.chipData.dg2 } : {}),
69
+ chipAuth: state.chipData.chipAuth,
70
+ },
71
+ }
72
+ : {}),
73
+ // Only sent when the flow actually asked — an inactive questionnaire
74
+ // must not put an empty object on the verification record.
75
+ ...(hasActiveQuestionnaire(state.config.questionnaire)
76
+ ? {
77
+ questionnaire: questionnairePayload(
78
+ state.config.questionnaire!.fields,
79
+ state.questionnaireAnswers,
80
+ ),
81
+ }
82
+ : {}),
83
+ // `metadata` is free-form passthrough. The SDK-owned keys (`requestId` —
84
+ // the server's idempotency key — and `device`) are written AFTER the
85
+ // caller's metadata so consumer keys can never clobber them. The user
86
+ // reference is the top-level `userId` above. Identical on web + Flutter.
87
+ metadata: {
88
+ ...(state.config.metadata ?? {}),
89
+ requestId: generateRequestId(),
90
+ device: {
91
+ ...(collectDeviceMetadata() as unknown as Record<string, unknown>),
92
+ // Device Intelligence rides the free-form device channel rather
93
+ // than a schema field, so an older server simply ignores it and
94
+ // an older SDK degrades to IP + user-agent only. Off when the
95
+ // workflow disables it — collecting signals nobody will score is
96
+ // data taken for nothing.
97
+ ...(fingerprint ? { fingerprint } : {}),
98
+ },
99
+ },
100
+ };
101
+ }
@@ -0,0 +1,81 @@
1
+ // ---------------------------------------------------------------------------
2
+ // The APPLICANT's own verification — a SECOND, ordinary INDIVIDUAL submission
3
+ // fired after the business one succeeds, linked back to the application via
4
+ // `metadata.userId = applicantKeyPersonId` (the server-side contract that binds
5
+ // the applicant's KYC to their KeyPerson row). Mirrors the web SDK's
6
+ // submitApplicantVerification and Flutter's _submitApplicantVerification.
7
+ //
8
+ // When the org mapped an applicant workflow (business.applicant.workflowId,
9
+ // overlaid onto the config by the workflow gate), its id rides along so the
10
+ // server applies THAT workflow's gates, pricing and decision graph.
11
+ // ---------------------------------------------------------------------------
12
+
13
+ import { collectDeviceMetadata } from '../services/deviceMetadata';
14
+ import { generateRequestId } from '../utils/uuid';
15
+ import { splitFullName } from '../config/keyPeople';
16
+ import { effectiveCountry } from './derive';
17
+ import type { ClientFingerprint } from '../services/fingerprint';
18
+ import type { VerifyRequest } from '../services/api';
19
+ import type { KycState } from './state';
20
+
21
+ /** Whether the applicant capture leg actually produced something to submit. */
22
+ export function applicantMediaCaptured(state: KycState): boolean {
23
+ return Boolean(
24
+ state.selectedIdType &&
25
+ (state.mediaIds.selfie || state.mediaIds.documentFront || (state.idNumber ?? '').trim() !== ''),
26
+ );
27
+ }
28
+
29
+ export function buildApplicantVerifyRequest(
30
+ state: KycState,
31
+ applicantKeyPersonId: string,
32
+ fingerprint: ClientFingerprint | undefined,
33
+ ): VerifyRequest {
34
+ // Name: the consumer's userData wins; the applicant-role step's optional
35
+ // full name fills the gaps. Same precedence as the web SDK.
36
+ const cfgUser = state.config.userData;
37
+ const split = splitFullName((state.businessApplication.applicantName ?? '').trim()) ?? {};
38
+ const firstName = cfgUser?.firstName?.trim() || split.firstName;
39
+ const lastName = cfgUser?.lastName?.trim() || split.lastName;
40
+ const userData =
41
+ firstName || lastName
42
+ ? { ...(firstName ? { firstName } : {}), ...(lastName ? { lastName } : {}) }
43
+ : undefined;
44
+
45
+ return {
46
+ // The applicant's own leg country — their country-select choice (or the
47
+ // overlaid applicant workflow's / registry fallback), never forced to the
48
+ // business registry country.
49
+ country: effectiveCountry(state),
50
+ idType: state.selectedIdType ?? '',
51
+ ...(state.idNumber?.trim() ? { idNumber: state.idNumber } : {}),
52
+ ...(state.config.applicantWorkflowId ? { workflowId: state.config.applicantWorkflowId } : {}),
53
+ ...(userData ? { userData } : {}),
54
+ mediaIds: state.mediaIds,
55
+ // The chip read, when the leg ran the NFC step (an overlaid applicant
56
+ // workflow can enable it). Same block as the individual flow's builder.
57
+ ...(state.chipData
58
+ ? {
59
+ nfc: {
60
+ dg1: state.chipData.dg1,
61
+ ...(state.chipData.sod ? { sod: state.chipData.sod } : {}),
62
+ ...(state.chipData.dg2 ? { dg2: state.chipData.dg2 } : {}),
63
+ chipAuth: state.chipData.chipAuth,
64
+ },
65
+ }
66
+ : {}),
67
+ // NO top-level `userId` here on purpose: the server prefers it over
68
+ // metadata.userId, so sending the org's own user ref would sever the
69
+ // KeyPerson link this submission exists to make.
70
+ metadata: {
71
+ ...(state.config.metadata ?? {}),
72
+ requestId: generateRequestId(),
73
+ device: {
74
+ ...(collectDeviceMetadata() as unknown as Record<string, unknown>),
75
+ ...(fingerprint ? { fingerprint } : {}),
76
+ },
77
+ // The link back to the application — written last so nothing clobbers it.
78
+ userId: applicantKeyPersonId,
79
+ },
80
+ };
81
+ }
@@ -0,0 +1,113 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Presentation: branding, the copy an org can override, and voice guidance.
3
+ //
4
+ // Split from config.ts (200-line rule). These are the parts of the flow an org
5
+ // makes its own — colours, logo, the words on the consent and success screens —
6
+ // as opposed to what it VERIFIES, which lives in workflow.ts and business.ts.
7
+ // ---------------------------------------------------------------------------
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Appearance / theming
11
+ // ---------------------------------------------------------------------------
12
+
13
+ export interface KYCAppearance {
14
+ /** Brand color — drives buttons, selected states, focus rings. */
15
+ primaryColor?: string;
16
+ /** Text/icon color rendered on top of `primaryColor` (e.g. button labels). */
17
+ primaryTextColor?: string;
18
+ /** Accent color for subtle hover/active surfaces. */
19
+ accentColor?: string;
20
+ /** Modal/sheet background color. */
21
+ backgroundColor?: string;
22
+ /** Elevated surface color for cards/panels. */
23
+ surfaceColor?: string;
24
+ /** Border + input outline color. */
25
+ borderColor?: string;
26
+ /** Primary text color. */
27
+ textColor?: string;
28
+ /**
29
+ * Corner radius in px for buttons, inputs and cards — the whole scale derives
30
+ * from it, so 0 squares the flow off and 20 softens it. Circular elements
31
+ * (avatars, the camera frame) are never affected. Default 12.
32
+ */
33
+ borderRadius?: number;
34
+ /**
35
+ * Body font family.
36
+ *
37
+ * Unlike the web SDK this does NOT fetch the font: React Native renders from
38
+ * families registered by the HOST APP (the SDK's own Karla / Space Grotesk
39
+ * ship as bundled assets). Pass a family your app has loaded; an unknown name
40
+ * falls back to the SDK default rather than rendering nothing.
41
+ */
42
+ fontFamily?: string;
43
+ /** Heading font family. Falls back to {@link fontFamily} when only that is set. */
44
+ headingFontFamily?: string;
45
+ /**
46
+ * Colour overrides applied only in DARK mode.
47
+ *
48
+ * The colours above are the LIGHT palette. Without this block a branded flow
49
+ * keeps its light background after the theme toggle — the appearance is
50
+ * applied on top of whichever base scheme is active, so an org's light
51
+ * background simply overwrote the dark one.
52
+ */
53
+ dark?: Pick<
54
+ KYCAppearance,
55
+ | 'primaryColor'
56
+ | 'primaryTextColor'
57
+ | 'accentColor'
58
+ | 'backgroundColor'
59
+ | 'surfaceColor'
60
+ | 'borderColor'
61
+ | 'textColor'
62
+ >;
63
+ companyName?: string;
64
+ /**
65
+ * Logo to show in the flow.
66
+ * - An image URL renders that logo.
67
+ * - The literal `'default'` renders the org's own logo from the server config
68
+ * response (falls back to the built-in shield if the org has none set).
69
+ * - Omitted renders the built-in shield badge.
70
+ */
71
+ logo?: string;
72
+ /** Initial mode. 'system' follows the device's colour scheme (live, until
73
+ * the in-flow theme toggle picks one); applied on mount. */
74
+ theme?: 'light' | 'dark' | 'system';
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Consent / success screen content
79
+ // ---------------------------------------------------------------------------
80
+
81
+ export interface KYCConsentContent {
82
+ /** Consent heading. Supports `{firstName}` / `{lastName}` tokens. */
83
+ title?: string;
84
+ /** Sub-text under the heading. Same tokens. */
85
+ description?: string;
86
+ }
87
+
88
+ export interface KYCSuccessContent {
89
+ /** Success heading. Supports `{firstName}` / `{lastName}` tokens. */
90
+ title?: string;
91
+ /** Sub-text under the heading. Same tokens. */
92
+ description?: string;
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Voice guidance (spoken liveness instructions — TTS output only, no mic)
97
+ // ---------------------------------------------------------------------------
98
+
99
+ /**
100
+ * Configuration for the spoken liveness instructions. TTS **output** for
101
+ * accessibility — it never records audio, so no microphone permission is
102
+ * involved. An object (not a bare boolean) so a `language` can be added later
103
+ * without a breaking change.
104
+ */
105
+ export interface VoiceGuidanceConfig {
106
+ /** Whether spoken guidance plays. Default `true`. */
107
+ enabled?: boolean;
108
+ /** BCP-47 voice tag (e.g. `'en-US'`, `'fr-FR'`). Default `'en-US'`. */
109
+ language?: string;
110
+ }
111
+
112
+ /** Accepts a bare boolean for ergonomics or the full {@link VoiceGuidanceConfig}. */
113
+ export type VoiceGuidanceOption = boolean | VoiceGuidanceConfig;
@@ -0,0 +1,131 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Business (KYB) workflow types
3
+ //
4
+ // LIVE business verification is WORKFLOW-REQUIRED: the server only accepts a
5
+ // business submission with a published KYB workflow (workflowId or hosted
6
+ // link). `subjectType`/`business` also exist as public props for the dashboard
7
+ // builder's live preview (`previewMode`, where all writes are mocked).
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /** What a workflow verifies. Absent = 'individual' (classic KYC). */
11
+ export type SubjectType = 'individual' | 'business';
12
+
13
+ /** Associated-party roles discovered from the registry lookup. */
14
+ export type KeyPersonRole = 'director' | 'beneficial_owner' | 'signatory' | 'shareholder';
15
+
16
+ /** Roles the APPLICANT (the person submitting) may declare — key-person roles
17
+ * plus 'authorized_representative' (someone filing on the company's behalf). */
18
+ export type ApplicantRole = KeyPersonRole | 'authorized_representative';
19
+
20
+ /** Supporting-document kinds a KYB workflow can request from the applicant. */
21
+ export const COMPANY_INFO_FIELDS = ['address', 'email', 'phone', 'website'] as const;
22
+ export type CompanyInfoField = (typeof COMPANY_INFO_FIELDS)[number];
23
+ export type CompanyInfoMode = 'off' | 'optional' | 'required';
24
+
25
+ export type BusinessDocumentKey =
26
+ | 'incorporation_certificate'
27
+ | 'memart'
28
+ | 'proof_of_address'
29
+ | 'tax_document'
30
+ | 'regulatory_license'
31
+ | 'board_resolution'
32
+ | 'other';
33
+
34
+ /** Verification depth for discovered key people. */
35
+ export type KeyPeopleLevel = 'screening_only' | 'data' | 'full_kyc';
36
+
37
+ /**
38
+ * Key-people (associated-party) verification block on a KYB workflow —
39
+ * template config that arrives via workflow resolution. Mirrors the server's
40
+ * `KeyPeopleConfigSchema`; the SDK only reads the email-invite gate.
41
+ */
42
+ export interface WorkflowKeyPeopleConfig {
43
+ enabled?: boolean;
44
+ /** SDK collects the directors/owners from the applicant (adds the
45
+ * business-key-people step to the flow). */
46
+ collect?: boolean;
47
+ /** Minimum people the applicant must list when collect is on (0 = skippable). */
48
+ minEntries?: number;
49
+ /** In-scope roles. Absent/empty = all four roles. */
50
+ roles?: KeyPersonRole[];
51
+ /** Shareholders at/above this % escalate to beneficial owner (default 25). */
52
+ ownershipThreshold?: number;
53
+ /** Default verification depth when a role has no `perRole` override. */
54
+ level?: KeyPeopleLevel;
55
+ requirement?: 'all_in_scope' | 'ubos_only' | 'advisory';
56
+ /** Per-role verification depth overrides (win over `level`). */
57
+ perRole?: Partial<Record<KeyPersonRole, KeyPeopleLevel>>;
58
+ /** Invite distribution for full-KYC people. */
59
+ invite?: {
60
+ channel?: string;
61
+ /** The KYC workflow the per-person invite links run through. */
62
+ workflowId?: string;
63
+ };
64
+ }
65
+
66
+ /**
67
+ * A KYB workflow's business configuration block. Business verification is a
68
+ * registry lookup — no ID types, no capture — and the block carries its own
69
+ * registry countries (any provider-supported ISO-2 codes, NOT limited to the
70
+ * individual-KYC catalogue).
71
+ */
72
+ export interface WorkflowBusinessConfig {
73
+ /** ISO-2 PRIMARY registry country — the picker's default/pre-selected value. */
74
+ country: string;
75
+ /**
76
+ * Multi-registry: every country the visitor may pick from. Published
77
+ * workflows always carry an explicit list (the server materializes the
78
+ * "all supported countries" mode at publish); absent = just the primary.
79
+ */
80
+ countries?: string[];
81
+ /** Builder marker for "all supported countries" (materialized at publish). */
82
+ allCountries?: boolean;
83
+ /**
84
+ * Offered verification products (keys from the server's business-products
85
+ * catalog, e.g. 'business', 'business-tax'). Absent = just 'business'.
86
+ * The effective offering narrows per picked country (TIN is NG-only).
87
+ */
88
+ products?: string[];
89
+ /** Require the visitor to also type the registered business name. */
90
+ requireRegistrationName?: boolean;
91
+ /** Collect the company profile (address / email / phone / website) on the
92
+ * business-details step. Default ON; false hides the whole section. */
93
+ collectCompanyInfo?: boolean;
94
+ /** Per-field mode: absent = 'optional'; 'required' blocks Continue without
95
+ * it; 'off' hides the field. */
96
+ companyInfo?: Partial<Record<CompanyInfoField, CompanyInfoMode>>;
97
+ /** Key-people (director/owner) verification configuration. */
98
+ keyPeople?: WorkflowKeyPeopleConfig;
99
+ /** Supporting-document collection (business-documents step). */
100
+ documents?: WorkflowBusinessDocumentsConfig;
101
+ /** Applicant (submitter) identity verification (in-flow individual KYC). */
102
+ applicant?: WorkflowBusinessApplicantConfig;
103
+ }
104
+
105
+ /** One requested document slot on a KYB workflow's `documents` block. */
106
+ export interface WorkflowBusinessDocumentTypeConfig {
107
+ key: BusinessDocumentKey;
108
+ /** Display label override (defaults per key, e.g. "Certificate of incorporation"). */
109
+ label?: string;
110
+ /** Submission is blocked (422 missing_documents) until this slot is uploaded. */
111
+ required?: boolean;
112
+ }
113
+
114
+ /**
115
+ * Supporting-document collection block on a KYB workflow. `enabled` with
116
+ * absent/empty `types` defaults to just a required incorporation certificate.
117
+ */
118
+ export interface WorkflowBusinessDocumentsConfig {
119
+ enabled?: boolean;
120
+ types?: WorkflowBusinessDocumentTypeConfig[];
121
+ }
122
+
123
+ /**
124
+ * Applicant-verification block on a KYB workflow: when `verification` is true
125
+ * the applicant verifies their OWN identity in-flow (role declaration + the
126
+ * ordinary individual capture steps), linked back to the application via
127
+ * `metadata.userId = applicantKeyPersonId` on the second submission.
128
+ */
129
+ export interface WorkflowBusinessApplicantConfig {
130
+ verification?: boolean;
131
+ }