@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
|
@@ -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
|
+
}
|