@myazahq/kyc-sdk-react-native 2.4.0 → 2.6.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 +151 -5
- package/android/build.gradle +35 -3
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +77 -0
- package/app.plugin.js +89 -8
- package/ios/HybridMyazaFaceDetector.swift +16 -3
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +23 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +9 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +43 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +17 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +31 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +3 -0
- package/package.json +14 -2
- package/src/MyazaBiometricAuth.tsx +119 -0
- package/src/MyazaKYC.tsx +12 -0
- package/src/components/CountryField.tsx +8 -0
- package/src/components/CountryRegionPicker.tsx +184 -0
- package/src/components/DashedBorder.tsx +55 -0
- package/src/components/DialCodePicker.tsx +53 -67
- package/src/components/DialCodeRow.tsx +102 -0
- package/src/components/FramedMapPicker.tsx +122 -0
- package/src/components/GeoBadge.tsx +34 -0
- package/src/components/Icon.tsx +2 -176
- package/src/components/KycFlow.tsx +60 -32
- package/src/components/LineSkeleton.tsx +100 -0
- package/src/components/MapChrome.tsx +67 -0
- package/src/components/MapPinMarker.tsx +54 -0
- package/src/components/MapPinPicker.tsx +179 -0
- package/src/components/MilestoneTrack.tsx +155 -0
- package/src/components/MultiIdProgress.tsx +107 -0
- package/src/components/MyazaInput.tsx +39 -4
- package/src/components/MyazaSelect.tsx +2 -1
- package/src/components/OptionRow.tsx +22 -3
- package/src/components/OwnershipSlider.tsx +102 -0
- package/src/components/PhoneNumberInput.tsx +45 -4
- package/src/components/PoweredBy.tsx +5 -5
- package/src/components/PresenceBlocks.tsx +153 -0
- package/src/components/StaggerIn.tsx +63 -0
- package/src/components/StepView.tsx +43 -0
- package/src/components/StickyActions.tsx +56 -0
- package/src/components/dialCodeRows.ts +64 -0
- package/src/components/glass/FloatingSheet.tsx +73 -21
- package/src/components/glass/GlassSheet.tsx +14 -2
- package/src/components/icon-map.ts +176 -0
- package/src/components/stepHeaderMeta.tsx +45 -5
- package/src/config/addressCollection.ts +125 -0
- package/src/config/biometricOptions.ts +100 -0
- package/src/config/business.ts +26 -7
- package/src/config/businessDetailsValidity.ts +52 -0
- package/src/config/businessPrefill.ts +72 -0
- package/src/config/businessSteps.ts +20 -3
- package/src/config/consentStep.ts +19 -0
- package/src/config/keyPeople.ts +111 -7
- package/src/config/keyPeopleCorporate.ts +19 -0
- package/src/config/keyPeopleOwnerTypes.ts +12 -0
- package/src/config/keyPeopleOwners.ts +26 -0
- package/src/config/keyPeoplePrefill.ts +138 -0
- package/src/config/keyPeopleSectionDefs.ts +65 -0
- package/src/config/keyPeopleSections.ts +167 -0
- package/src/config/proofOfAddress.ts +54 -11
- package/src/config/regions.ts +24 -0
- package/src/config/registrationHint.ts +1 -1
- package/src/config/stepOrder.ts +100 -6
- package/src/config/theme.ts +8 -2
- package/src/config/uploadLimits.ts +35 -0
- package/src/config/website.ts +36 -0
- package/src/config/workflowMerge.ts +16 -5
- package/src/emrtd/activeAuth.ts +100 -0
- package/src/emrtd/extras.ts +76 -0
- package/src/emrtd/files.ts +5 -0
- package/src/emrtd/index.ts +1 -0
- package/src/emrtd/read.ts +8 -1
- package/src/emrtd/session.ts +41 -0
- package/src/emrtd/stages.ts +11 -0
- package/src/index.ts +54 -1
- package/src/lib/address-current-location.ts +171 -0
- package/src/lib/address-field-modes.ts +119 -0
- package/src/lib/address-flow.ts +172 -0
- package/src/lib/address-helpers.ts +48 -0
- package/src/lib/address-line.ts +76 -0
- package/src/lib/address-pin-move.ts +97 -0
- package/src/lib/address-step-recovery.ts +63 -0
- package/src/lib/authed-image.ts +78 -0
- package/src/lib/biometric-auth.ts +52 -0
- package/src/lib/biometric-copy.ts +51 -0
- package/src/lib/captureRing.ts +83 -0
- package/src/lib/contact-recovery.ts +44 -0
- package/src/lib/country-adoption.ts +89 -0
- package/src/lib/inferred-country.ts +65 -0
- package/src/lib/livenessLayout.ts +49 -0
- package/src/lib/map-frame.ts +159 -0
- package/src/lib/map-tiles.ts +143 -0
- package/src/lib/multi-id.ts +210 -0
- package/src/lib/poa-country-gate.ts +27 -0
- package/src/lib/resubmit.ts +130 -0
- package/src/lib/result-copy.ts +116 -0
- package/src/lib/result-wait.ts +53 -0
- package/src/lib/review-map-surface.ts +26 -0
- package/src/lib/scope.ts +31 -0
- package/src/lib/screen-corners.ts +50 -0
- package/src/lib/selfie-upload-wait.ts +79 -0
- package/src/lib/step-log.ts +24 -3
- package/src/lib/street-view-fov.ts +42 -0
- package/src/lib/use-multi-id-plan.ts +29 -0
- package/src/lib/webview-available.ts +47 -0
- package/src/liveness/useLiveness.ts +6 -1
- package/src/liveness/useModelReady.ts +65 -0
- package/src/liveness/visionCameraFaceDetector.ts +35 -0
- package/src/presence/background-math.ts +100 -0
- package/src/presence/background-store.ts +82 -0
- package/src/presence/background.ts +165 -0
- package/src/presence/foreground-service.ts +193 -0
- package/src/presence/fs.ts +51 -0
- package/src/presence/geofence.ts +41 -0
- package/src/presence/math.ts +44 -0
- package/src/presence/post.ts +40 -0
- package/src/presence/report.ts +87 -0
- package/src/presence/sampler.ts +110 -0
- package/src/presence/status.ts +92 -0
- package/src/presence/store.ts +94 -0
- package/src/presence/tier.ts +36 -0
- package/src/presence/watch-wait.ts +81 -0
- package/src/screens/AddressCountryControl.tsx +115 -0
- package/src/screens/ApplicantRoleStep.tsx +9 -5
- package/src/screens/BusinessCheckPanel.tsx +89 -0
- package/src/screens/BusinessDetailsFields.tsx +112 -0
- package/src/screens/BusinessDetailsStep.tsx +133 -145
- package/src/screens/BusinessDocumentSlot.tsx +5 -5
- package/src/screens/BusinessDocumentsStep.tsx +5 -56
- package/src/screens/BusinessKeyPeopleStep.tsx +103 -71
- package/src/screens/BusinessPickedCard.tsx +86 -0
- package/src/screens/BusinessPickedSection.tsx +51 -0
- package/src/screens/BusinessRegistryPickers.tsx +67 -0
- package/src/screens/BusinessSandboxToggle.tsx +114 -0
- package/src/screens/BusinessSearch.tsx +199 -0
- package/src/screens/BusinessSearchResults.tsx +115 -0
- package/src/screens/CompanyInfoFields.tsx +91 -51
- package/src/screens/ConsentStep.tsx +37 -17
- package/src/screens/ContactActions.tsx +54 -0
- package/src/screens/ContactDestinationField.tsx +5 -0
- package/src/screens/ContactEntryPanel.tsx +75 -0
- package/src/screens/ContactVerificationStep.tsx +43 -42
- package/src/screens/CountrySelectStep.tsx +13 -147
- package/src/screens/IdTypeStep.tsx +12 -5
- package/src/screens/KeepLinksSheet.tsx +88 -0
- package/src/screens/KeyPeopleAwaitCard.tsx +204 -0
- package/src/screens/KeyPeopleAwaitList.tsx +126 -0
- package/src/screens/KeyPeoplePending.tsx +154 -0
- package/src/screens/KeyPeopleSection.tsx +162 -0
- package/src/screens/KeyPeopleSectionsList.tsx +103 -0
- package/src/screens/KeyPeopleUboExemption.tsx +72 -0
- package/src/screens/KeyPersonCard.tsx +36 -4
- package/src/screens/KeyPersonForm.tsx +195 -36
- package/src/screens/KeyPersonKindToggle.tsx +68 -0
- package/src/screens/KeyPersonOwners.tsx +109 -0
- package/src/screens/KeyPersonRoleChips.tsx +68 -0
- package/src/screens/KeyPersonSheet.tsx +92 -108
- package/src/screens/LivenessAvatar.tsx +15 -3
- package/src/screens/LivenessStep.tsx +172 -52
- package/src/screens/NfcStep.tsx +14 -0
- package/src/screens/PoaDocumentTypeList.tsx +66 -0
- package/src/screens/ProofOfAddressParts.tsx +151 -0
- package/src/screens/ProofOfAddressStep.tsx +58 -96
- package/src/screens/SubmittedBadge.tsx +25 -0
- package/src/screens/SubmittedError.tsx +64 -0
- package/src/screens/SubmittedResult.tsx +117 -0
- package/src/screens/SubmittedStep.tsx +82 -119
- package/src/screens/SubmittedSuccess.tsx +127 -0
- package/src/screens/SubmittedWaiting.tsx +45 -0
- package/src/screens/address/AddressEntranceStep.tsx +161 -0
- package/src/screens/address/AddressIntroGate.tsx +148 -0
- package/src/screens/address/AddressMapStub.tsx +50 -0
- package/src/screens/address/AddressPinStep.tsx +178 -0
- package/src/screens/address/AddressReviewStep.tsx +162 -0
- package/src/screens/address/AddressSandboxTabs.tsx +158 -0
- package/src/screens/address/AddressSearchField.tsx +115 -0
- package/src/screens/address/AddressSearchStep.tsx +94 -0
- package/src/screens/address/CurrentLocationRow.tsx +135 -0
- package/src/screens/address/DetailsSheet.tsx +175 -0
- package/src/screens/address/DetailsSheetFields.tsx +184 -0
- package/src/screens/address/EntranceDropzone.tsx +178 -0
- package/src/screens/address/EntranceFraming.tsx +66 -0
- package/src/screens/address/EntrancePills.tsx +60 -0
- package/src/screens/address/FramedStreetView.tsx +155 -0
- package/src/screens/address/IntroDisclosures.tsx +210 -0
- package/src/screens/address/LabelDecisionRow.tsx +113 -0
- package/src/screens/address/PinSummaryRow.tsx +105 -0
- package/src/screens/address/ReviewAddressBand.tsx +111 -0
- package/src/screens/address/ReviewEntranceThumbs.tsx +64 -0
- package/src/screens/address/ReviewMapPicture.tsx +106 -0
- package/src/screens/address/SearchResults.tsx +105 -0
- package/src/screens/address/SearchScreen.tsx +176 -0
- package/src/screens/address/SkipForNow.tsx +43 -0
- package/src/screens/address/StreetViewChrome.tsx +81 -0
- package/src/screens/address/detail-values.ts +22 -0
- package/src/screens/address/fix-source.ts +27 -0
- package/src/screens/address/index.ts +8 -0
- package/src/screens/address/meta.ts +44 -0
- package/src/screens/address/use-address-flow.ts +200 -0
- package/src/screens/address/use-label-pin.ts +80 -0
- package/src/screens/address/use-pin-actions.ts +192 -0
- package/src/screens/biometric/BiometricAuthFlow.tsx +170 -0
- package/src/screens/consent/model.ts +79 -10
- package/src/screens/liveness/CaptureRing.tsx +91 -0
- package/src/screens/liveness/LivenessCamera.tsx +63 -0
- package/src/screens/liveness/LivenessHandover.tsx +43 -0
- package/src/screens/liveness/LivenessOutcome.tsx +4 -3
- package/src/screens/liveness/SelfiePreview.tsx +13 -4
- package/src/screens/liveness/ShutterFlash.tsx +25 -0
- package/src/screens/liveness/index.ts +4 -0
- package/src/screens/liveness/useSelfieUpload.ts +23 -3
- package/src/screens/nfc/NfcReadProgress.tsx +1 -0
- package/src/screens/nfc/NfcScanIllustration.tsx +7 -18
- package/src/screens/useAddressPhotoAttach.ts +77 -0
- package/src/screens/useAwaitingPeople.ts +90 -0
- package/src/screens/useBusinessDocumentAttach.ts +93 -0
- package/src/screens/usePoaAttach.ts +12 -15
- package/src/services/api-biometric.ts +37 -0
- package/src/services/api-types-biometric.ts +41 -0
- package/src/services/api-types.ts +251 -3
- package/src/services/api-verify-types.ts +90 -1
- package/src/services/api.ts +237 -3
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/errors.ts +10 -2
- package/src/services/location.ts +234 -0
- package/src/services/uploadErrors.ts +30 -0
- package/src/services/workflowGate.ts +8 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +29 -0
- package/src/store/address.ts +97 -0
- package/src/store/businessCheck.ts +113 -0
- package/src/store/derive.ts +44 -4
- package/src/store/kycStore.ts +352 -16
- package/src/store/serverConfig.ts +20 -0
- package/src/store/session.ts +227 -0
- package/src/store/state.ts +289 -2
- package/src/store/submit.ts +80 -13
- package/src/store/submitApplicant.ts +5 -11
- package/src/types/business.ts +23 -2
- package/src/types/config.ts +70 -2
- package/src/types/verification.ts +13 -1
- package/src/types/workflow.ts +112 -1
- package/src/screens/KeyPeopleInviteLinks.tsx +0 -239
package/src/store/kycStore.ts
CHANGED
|
@@ -22,13 +22,45 @@ import { INITIAL_SERVER_CONFIG, describeConfigError, type ServerConfigState } fr
|
|
|
22
22
|
import {
|
|
23
23
|
EMPTY_BUSINESS,
|
|
24
24
|
EMPTY_BUSINESS_APPLICATION,
|
|
25
|
+
EMPTY_BUSINESS_CHECK,
|
|
25
26
|
type KYCSubmissionResult,
|
|
26
27
|
type KycState,
|
|
27
28
|
type KycStore,
|
|
28
29
|
} from './state';
|
|
29
|
-
import { nextStepAfter, nfcDecision, previousStepBefore } from './derive';
|
|
30
|
+
import { nextStepAfter, nfcDecision, openingStep, previousStepBefore } from './derive';
|
|
31
|
+
|
|
32
|
+
/** The steps that make up ONE ID's evidence. Leaving this set is what ends a
|
|
33
|
+
* multi-ID check — the leg has several exits depending on the ID. */
|
|
34
|
+
const ID_EVIDENCE_STEPS = new Set<KYCStep>(['id-input', 'document-capture', 'nfc']);
|
|
35
|
+
|
|
36
|
+
/** The 1-based check the applicant is on, or undefined outside a multi-ID run.
|
|
37
|
+
* Only emitted once a slot has been COMMITTED — on an ordinary run the field
|
|
38
|
+
* would be a constant 1 on every entry, which is noise. */
|
|
39
|
+
function multiIdSlotOf(s: KycState): number | undefined {
|
|
40
|
+
return s.multiIdSlots.length > 0 ? s.multiIdSlots.length + 1 : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The ID selected at this moment — see StepLogEntry.idType. */
|
|
44
|
+
function selectedIdTypeOf(s: KycState): string | undefined {
|
|
45
|
+
return s.selectedIdType ?? undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The active multi-ID plan for the store's current state, or null. */
|
|
49
|
+
function multiIdPlanFor(s: KycState): ReturnType<typeof multiIdPlan> {
|
|
50
|
+
return multiIdPlan(
|
|
51
|
+
{ ...s.config, country: s.selectedCountry ?? s.config.country },
|
|
52
|
+
{ multiIdSlotIndex: s.multiIdSlotIndex, multiIdSlots: s.multiIdSlots },
|
|
53
|
+
s.serverConfig.status === 'ready' ? s.serverConfig.idTypes : [],
|
|
54
|
+
);
|
|
55
|
+
}
|
|
30
56
|
import { recordStep, resetStepLog } from '../lib/step-log';
|
|
57
|
+
import { resetAutoLocate, resetCurrentFix } from '../lib/address-current-location';
|
|
58
|
+
import { multiIdPlan } from '../lib/multi-id';
|
|
59
|
+
import { resolveIdTypeDefinition } from '../config/idTypes';
|
|
31
60
|
import { buildVerifyRequest } from './submit';
|
|
61
|
+
import { IDLE_SELFIE_UPLOAD } from '../lib/selfie-upload-wait';
|
|
62
|
+
import { resetBusinessCheck, runBusinessCheck } from './businessCheck';
|
|
63
|
+
import { startAttemptSession, watchSessionProgress } from './session';
|
|
32
64
|
import { applicantMediaCaptured, buildApplicantVerifyRequest } from './submitApplicant';
|
|
33
65
|
|
|
34
66
|
export * from './state';
|
|
@@ -65,9 +97,18 @@ export function createKycStore(
|
|
|
65
97
|
api,
|
|
66
98
|
|
|
67
99
|
currentStep: 'consent',
|
|
100
|
+
sessionId: null,
|
|
101
|
+
sessionUrl: null,
|
|
102
|
+
businessCheck: { ...EMPTY_BUSINESS_CHECK },
|
|
68
103
|
selectedCountry: null,
|
|
104
|
+
countryAutoPicked: false,
|
|
69
105
|
selectedIdType: null,
|
|
70
106
|
idNumber: null,
|
|
107
|
+
multiIdSlotIndex: 0,
|
|
108
|
+
multiIdSlots: [],
|
|
109
|
+
multiIdRestored: null,
|
|
110
|
+
selfiePreviewUri: null,
|
|
111
|
+
selfieUpload: IDLE_SELFIE_UPLOAD,
|
|
71
112
|
mediaIds: {},
|
|
72
113
|
submissionResult: null,
|
|
73
114
|
serverConfig: serverConfig ?? INITIAL_SERVER_CONFIG,
|
|
@@ -88,6 +129,11 @@ export function createKycStore(
|
|
|
88
129
|
chipData: null,
|
|
89
130
|
poaDocumentType: null,
|
|
90
131
|
poaFileName: null,
|
|
132
|
+
address: null,
|
|
133
|
+
addressPhotoPreview: null,
|
|
134
|
+
addressIntroSeen: false,
|
|
135
|
+
addressEntranceFraming: false,
|
|
136
|
+
addressSandboxOutcome: null,
|
|
91
137
|
isLoading: false,
|
|
92
138
|
error: null,
|
|
93
139
|
|
|
@@ -101,15 +147,27 @@ export function createKycStore(
|
|
|
101
147
|
const branding = res.branding
|
|
102
148
|
? { ...res.branding, logo: normalizeDevAssetUrl(res.branding.logo, baseUrl) }
|
|
103
149
|
: res.branding;
|
|
150
|
+
// The facts that just landed can add a step AHEAD of the one the flow
|
|
151
|
+
// opened on (the address search step, on a consent-less address
|
|
152
|
+
// flow). Someone still standing on the placeholder's opening step,
|
|
153
|
+
// having done nothing, is moved to the real one; anyone who has
|
|
154
|
+
// moved is left alone. Mirrors Flutter's _loadServerConfig.
|
|
155
|
+
const before = openingStep(get());
|
|
104
156
|
set({
|
|
105
157
|
serverConfig: {
|
|
106
158
|
status: 'ready',
|
|
107
159
|
idTypes: res.idTypes,
|
|
108
160
|
branding,
|
|
161
|
+
geoCountry: res.geoCountry,
|
|
162
|
+
addressSearch: res.addressSearch,
|
|
163
|
+
addressSearchMode: res.addressSearchMode,
|
|
164
|
+
mapsFrameUrl: res.mapsFrameUrl ?? null,
|
|
109
165
|
environment: res.environment,
|
|
110
166
|
fatal: false,
|
|
111
167
|
},
|
|
112
168
|
});
|
|
169
|
+
const after = openingStep(get());
|
|
170
|
+
if (get().currentStep === before && after !== before) set({ currentStep: after });
|
|
113
171
|
} catch (err) {
|
|
114
172
|
const described = describeConfigError(err);
|
|
115
173
|
set((s) => ({
|
|
@@ -122,11 +180,96 @@ export function createKycStore(
|
|
|
122
180
|
// Changing country invalidates the ID choice: the same key can mean a
|
|
123
181
|
// different document (or none) elsewhere, so it is cleared rather than
|
|
124
182
|
// silently carried across.
|
|
125
|
-
set((s) =>
|
|
126
|
-
selectedCountry
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
183
|
+
set((s) => {
|
|
184
|
+
const same = s.selectedCountry === country;
|
|
185
|
+
return {
|
|
186
|
+
selectedCountry: country,
|
|
187
|
+
countryAutoPicked: false,
|
|
188
|
+
selectedIdType: same ? s.selectedIdType : null,
|
|
189
|
+
idNumber: same ? s.idNumber : null,
|
|
190
|
+
// A multi-ID run's committed slots belong to the country they were
|
|
191
|
+
// picked in; carrying them into a new one would offer an ID no
|
|
192
|
+
// register there can verify.
|
|
193
|
+
multiIdSlotIndex: same ? s.multiIdSlotIndex : 0,
|
|
194
|
+
multiIdSlots: same ? s.multiIdSlots : [],
|
|
195
|
+
multiIdRestored: same ? s.multiIdRestored : null,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
setCountryAuto(country) {
|
|
201
|
+
get().setCountry(country);
|
|
202
|
+
if (!get().countryAutoPicked) set({ countryAutoPicked: true });
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
commitMultiIdSlot(nextStep, previews) {
|
|
206
|
+
set((s) => {
|
|
207
|
+
if (!s.selectedIdType) return {};
|
|
208
|
+
const slot = {
|
|
209
|
+
idType: s.selectedIdType,
|
|
210
|
+
...(s.idNumber ? { idNumber: s.idNumber } : {}),
|
|
211
|
+
...(s.mediaIds.documentFront ? { documentFront: s.mediaIds.documentFront } : {}),
|
|
212
|
+
...(s.mediaIds.documentBack ? { documentBack: s.mediaIds.documentBack } : {}),
|
|
213
|
+
...(s.mediaIds.documentFrontVideo
|
|
214
|
+
? { documentFrontVideo: s.mediaIds.documentFrontVideo }
|
|
215
|
+
: {}),
|
|
216
|
+
...(s.mediaIds.documentBackVideo
|
|
217
|
+
? { documentBackVideo: s.mediaIds.documentBackVideo }
|
|
218
|
+
: {}),
|
|
219
|
+
...(s.chipData ? { chipData: s.chipData } : {}),
|
|
220
|
+
...(previews?.front ? { documentFrontImage: previews.front } : {}),
|
|
221
|
+
...(previews?.back ? { documentBackImage: previews.back } : {}),
|
|
222
|
+
};
|
|
223
|
+
// The working evidence is cleared for the next check; the SELFIE and
|
|
224
|
+
// its video are run-level and deliberately untouched.
|
|
225
|
+
return {
|
|
226
|
+
multiIdSlots: [...s.multiIdSlots, slot],
|
|
227
|
+
multiIdSlotIndex: s.multiIdSlotIndex + 1,
|
|
228
|
+
selectedIdType: null,
|
|
229
|
+
idNumber: null,
|
|
230
|
+
multiIdRestored: null,
|
|
231
|
+
mediaIds: {
|
|
232
|
+
...s.mediaIds,
|
|
233
|
+
documentFront: undefined,
|
|
234
|
+
documentBack: undefined,
|
|
235
|
+
documentFrontVideo: undefined,
|
|
236
|
+
documentBackVideo: undefined,
|
|
237
|
+
},
|
|
238
|
+
// The chip belongs to the check just committed; the next one reads
|
|
239
|
+
// its own document (or none).
|
|
240
|
+
chipData: null,
|
|
241
|
+
documentScanPhase: 'front' as const,
|
|
242
|
+
currentStep: nextStep,
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
uncommitMultiIdSlot(step) {
|
|
248
|
+
set((s) => {
|
|
249
|
+
const last = s.multiIdSlots[s.multiIdSlots.length - 1];
|
|
250
|
+
if (!last) return {};
|
|
251
|
+
// Restore what that check captured — changing an earlier ID must not
|
|
252
|
+
// mean re-photographing a document that is still perfectly good.
|
|
253
|
+
return {
|
|
254
|
+
multiIdSlots: s.multiIdSlots.slice(0, -1),
|
|
255
|
+
multiIdSlotIndex: Math.max(s.multiIdSlots.length - 1, 0),
|
|
256
|
+
selectedIdType: last.idType,
|
|
257
|
+
idNumber: last.idNumber ?? null,
|
|
258
|
+
multiIdRestored: {
|
|
259
|
+
...(last.documentFrontImage ? { front: last.documentFrontImage } : {}),
|
|
260
|
+
...(last.documentBackImage ? { back: last.documentBackImage } : {}),
|
|
261
|
+
},
|
|
262
|
+
mediaIds: {
|
|
263
|
+
...s.mediaIds,
|
|
264
|
+
documentFront: last.documentFront,
|
|
265
|
+
documentBack: last.documentBack,
|
|
266
|
+
documentFrontVideo: last.documentFrontVideo,
|
|
267
|
+
documentBackVideo: last.documentBackVideo,
|
|
268
|
+
},
|
|
269
|
+
chipData: last.chipData ?? null,
|
|
270
|
+
currentStep: step,
|
|
271
|
+
};
|
|
272
|
+
});
|
|
130
273
|
},
|
|
131
274
|
|
|
132
275
|
setIdType(idType) {
|
|
@@ -141,6 +284,22 @@ export function createKycStore(
|
|
|
141
284
|
set((s) => ({ mediaIds: { ...s.mediaIds, [key]: mediaId } }));
|
|
142
285
|
},
|
|
143
286
|
|
|
287
|
+
setSelfiePreview(uri) {
|
|
288
|
+
set({ selfiePreviewUri: uri });
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
clearSelfie() {
|
|
292
|
+
set((s) => ({
|
|
293
|
+
selfiePreviewUri: null,
|
|
294
|
+
selfieUpload: IDLE_SELFIE_UPLOAD,
|
|
295
|
+
mediaIds: { ...s.mediaIds, selfie: undefined, livenessVideo: undefined },
|
|
296
|
+
}));
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
setSelfieUpload(upload) {
|
|
300
|
+
set({ selfieUpload: upload });
|
|
301
|
+
},
|
|
302
|
+
|
|
144
303
|
setDocumentMediaId(mediaId, side) {
|
|
145
304
|
set((s) => ({
|
|
146
305
|
mediaIds: {
|
|
@@ -163,11 +322,29 @@ export function createKycStore(
|
|
|
163
322
|
},
|
|
164
323
|
|
|
165
324
|
setContactVerified(channel, destination, token) {
|
|
325
|
+
set((s) => {
|
|
326
|
+
// A fresh proof clears its channel's "server refused this" flag.
|
|
327
|
+
const expired = (s.contact.expired ?? []).filter((c) => c !== channel);
|
|
328
|
+
return {
|
|
329
|
+
contact:
|
|
330
|
+
channel === 'email'
|
|
331
|
+
? { ...s.contact, emailAddress: destination, emailToken: token, expired }
|
|
332
|
+
: { ...s.contact, phoneNumber: destination, phoneToken: token, expired },
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
clearContactProofs(channels) {
|
|
338
|
+
// The server refused these proofs at submit (single-use tokens expire
|
|
339
|
+
// ~30 min after the OTP check, and session restore can resurrect a
|
|
340
|
+
// dead one). Drop the tokens, keep the destinations, flag the steps.
|
|
166
341
|
set((s) => ({
|
|
167
|
-
contact:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
342
|
+
contact: {
|
|
343
|
+
...s.contact,
|
|
344
|
+
...(channels.includes('email') ? { emailToken: undefined } : {}),
|
|
345
|
+
...(channels.includes('phone') ? { phoneToken: undefined } : {}),
|
|
346
|
+
expired: channels,
|
|
347
|
+
},
|
|
171
348
|
}));
|
|
172
349
|
},
|
|
173
350
|
|
|
@@ -180,14 +357,54 @@ export function createKycStore(
|
|
|
180
357
|
}));
|
|
181
358
|
},
|
|
182
359
|
|
|
360
|
+
setSessionId(sessionId, sessionUrl) {
|
|
361
|
+
set({ sessionId, ...(sessionUrl !== undefined ? { sessionUrl } : {}) });
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
async checkBusiness() {
|
|
365
|
+
return runBusinessCheck(get, set);
|
|
366
|
+
},
|
|
367
|
+
|
|
183
368
|
setBusinessField(key, value) {
|
|
184
|
-
|
|
369
|
+
// Any change to WHICH company this is about invalidates the answer we
|
|
370
|
+
// hold, so the check never describes one business while the field names
|
|
371
|
+
// another. Everything the previous register told us about the old
|
|
372
|
+
// company goes with it — only what the REGISTER wrote: an applicant who
|
|
373
|
+
// typed their own address meant it. Clearing it also unblocks the next
|
|
374
|
+
// lookup, whose prefill only ever writes into an empty field, so
|
|
375
|
+
// leftovers were not merely stale, they were suppressing the real
|
|
376
|
+
// answer. Mirrors the web SDK's setDetails.
|
|
377
|
+
const identityChanged =
|
|
378
|
+
key === 'registrationNumber' || key === 'country' || key === 'product';
|
|
379
|
+
set((s) => {
|
|
380
|
+
const business = { ...s.business, [key]: value };
|
|
381
|
+
if (identityChanged) {
|
|
382
|
+
for (const prefilledKey of s.businessCheck.prefilled) {
|
|
383
|
+
if (prefilledKey !== key) (business as Record<string, unknown>)[prefilledKey] = '';
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return { business };
|
|
387
|
+
});
|
|
388
|
+
if (identityChanged) resetBusinessCheck(set);
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
applyBusinessPrefill(patch, prefilled) {
|
|
392
|
+
set((s) => ({
|
|
393
|
+
business: { ...s.business, ...patch },
|
|
394
|
+
businessCheck: { ...s.businessCheck, prefilled },
|
|
395
|
+
}));
|
|
185
396
|
},
|
|
186
397
|
|
|
187
398
|
setKeyPeople(rows) {
|
|
188
399
|
set((s) => ({ businessApplication: { ...s.businessApplication, keyPeople: rows } }));
|
|
189
400
|
},
|
|
190
401
|
|
|
402
|
+
setUboUnidentifiable(checked) {
|
|
403
|
+
set((s) => ({
|
|
404
|
+
businessApplication: { ...s.businessApplication, uboUnidentifiable: checked },
|
|
405
|
+
}));
|
|
406
|
+
},
|
|
407
|
+
|
|
191
408
|
setBusinessDocument(doc) {
|
|
192
409
|
set((s) => ({
|
|
193
410
|
businessApplication: {
|
|
@@ -249,6 +466,41 @@ export function createKycStore(
|
|
|
249
466
|
});
|
|
250
467
|
},
|
|
251
468
|
|
|
469
|
+
setAddress(address) {
|
|
470
|
+
set({ address });
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
setAddressSandboxOutcome(addressSandboxOutcome) {
|
|
474
|
+
set({ addressSandboxOutcome });
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
setAddressPhoto(mediaId) {
|
|
478
|
+
set((s) => {
|
|
479
|
+
if (mediaId) return { mediaIds: { ...s.mediaIds, addressPhoto: mediaId } };
|
|
480
|
+
const { addressPhoto: _dropped, ...rest } = s.mediaIds;
|
|
481
|
+
return { mediaIds: rest };
|
|
482
|
+
});
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
setAddressPhotoPreview(uri) {
|
|
486
|
+
set({ addressPhotoPreview: uri });
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
markAddressIntroSeen() {
|
|
490
|
+
set({ addressIntroSeen: true });
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
setAddressEntranceFraming(framing) {
|
|
494
|
+
if (get().addressEntranceFraming !== framing) set({ addressEntranceFraming: framing });
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
clearAddress() {
|
|
498
|
+
set((s) => {
|
|
499
|
+
const { addressPhoto: _dropped, ...rest } = s.mediaIds;
|
|
500
|
+
return { address: null, mediaIds: rest, addressPhotoPreview: null };
|
|
501
|
+
});
|
|
502
|
+
},
|
|
503
|
+
|
|
252
504
|
setDocumentCapturePhase(phase) {
|
|
253
505
|
if (get().documentCapturePhase !== phase) set({ documentCapturePhase: phase });
|
|
254
506
|
},
|
|
@@ -276,6 +528,26 @@ export function createKycStore(
|
|
|
276
528
|
|
|
277
529
|
nextStep() {
|
|
278
530
|
const next = nextStepAfter(get().currentStep, get());
|
|
531
|
+
|
|
532
|
+
// Multi-ID: the run walks the capture leg once PER ID. Intercepted here
|
|
533
|
+
// rather than in each screen because it is one rule — "the applicant
|
|
534
|
+
// finished this check" — and the leg has several exits (a number-only
|
|
535
|
+
// ID leaves from id-input, a document ID from document-capture or the
|
|
536
|
+
// chip read after it).
|
|
537
|
+
const plan = multiIdPlanFor(get());
|
|
538
|
+
if (plan && ID_EVIDENCE_STEPS.has(get().currentStep) && !ID_EVIDENCE_STEPS.has(next)) {
|
|
539
|
+
const previews = get().multiIdRestored ?? undefined;
|
|
540
|
+
if (!plan.last) {
|
|
541
|
+
// Another ID to go: commit this one and hand the picker back.
|
|
542
|
+
get().commitMultiIdSlot('id-type', previews);
|
|
543
|
+
emitStepChange('id-type');
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// The final check: commit it, then carry on to the shared selfie.
|
|
547
|
+
get().commitMultiIdSlot(next, previews);
|
|
548
|
+
emitStepChange(next);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
279
551
|
// Leaving document capture is the moment the chip step either appears or
|
|
280
552
|
// silently does not. Four independent gates can remove it and a missing
|
|
281
553
|
// step looks the same however it went missing, so say which one.
|
|
@@ -294,6 +566,22 @@ export function createKycStore(
|
|
|
294
566
|
},
|
|
295
567
|
|
|
296
568
|
previousStep() {
|
|
569
|
+
// Multi-ID: stepping back from the picker means re-doing the PREVIOUS
|
|
570
|
+
// check, not leaving the flow. The slot is uncommitted so its ID number
|
|
571
|
+
// and captures come back — changing an earlier ID must not mean
|
|
572
|
+
// re-photographing a document that is still perfectly good.
|
|
573
|
+
const slots = get().multiIdSlots;
|
|
574
|
+
if (get().currentStep === 'id-type' && slots.length > 0) {
|
|
575
|
+
const last = slots[slots.length - 1]!;
|
|
576
|
+
const def = resolveIdTypeDefinition(
|
|
577
|
+
get().selectedCountry ?? get().config.country ?? '',
|
|
578
|
+
last.idType,
|
|
579
|
+
);
|
|
580
|
+
const step: KYCStep = def?.requiresDocumentCapture === false ? 'id-input' : 'document-capture';
|
|
581
|
+
get().uncommitMultiIdSlot(step);
|
|
582
|
+
emitStepChange(step);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
297
585
|
const prev = previousStepBefore(get().currentStep, get());
|
|
298
586
|
if (prev !== get().currentStep) {
|
|
299
587
|
set({ currentStep: prev, immersiveCapture: false, navDirection: 'back' });
|
|
@@ -322,7 +610,7 @@ export function createKycStore(
|
|
|
322
610
|
const res = await withRetry(() => api.verify(request), { onRetry });
|
|
323
611
|
const result: KYCSubmissionResult = {
|
|
324
612
|
verificationId: res.verificationId,
|
|
325
|
-
status: '
|
|
613
|
+
status: 'processing',
|
|
326
614
|
};
|
|
327
615
|
set({
|
|
328
616
|
submissionResult: result,
|
|
@@ -359,18 +647,36 @@ export function createKycStore(
|
|
|
359
647
|
// already sitting on 'consent' (the subscribe below only fires on
|
|
360
648
|
// change, and recordStep dedupes if it fires too).
|
|
361
649
|
resetStepLog();
|
|
362
|
-
|
|
650
|
+
// The cached GPS fix and the once-per-verification auto-locate are
|
|
651
|
+
// scoped to ONE verification: an app session spans many opens of the
|
|
652
|
+
// SDK, and a fix taken an hour ago would drop the pin at wherever the
|
|
653
|
+
// phone was then.
|
|
654
|
+
resetCurrentFix();
|
|
655
|
+
resetAutoLocate();
|
|
656
|
+
// The opening step never depends on the fields reset below (only later
|
|
657
|
+
// steps do), so it is safe to read before the write.
|
|
658
|
+
const opening = openingStep(get());
|
|
659
|
+
recordStep(opening, multiIdSlotOf(get()), selectedIdTypeOf(get()));
|
|
363
660
|
set({
|
|
364
|
-
currentStep:
|
|
661
|
+
currentStep: opening,
|
|
365
662
|
selectedCountry: null,
|
|
663
|
+
countryAutoPicked: false,
|
|
366
664
|
selectedIdType: null,
|
|
367
665
|
idNumber: null,
|
|
666
|
+
multiIdSlotIndex: 0,
|
|
667
|
+
multiIdSlots: [],
|
|
668
|
+
multiIdRestored: null,
|
|
669
|
+
selfiePreviewUri: null,
|
|
670
|
+
selfieUpload: IDLE_SELFIE_UPLOAD,
|
|
368
671
|
mediaIds: {},
|
|
369
672
|
submissionResult: null,
|
|
370
673
|
documentScanPhase: 'front',
|
|
371
674
|
documentCapturePhase: 'front',
|
|
372
675
|
immersiveCapture: false,
|
|
373
676
|
flashPaint: null,
|
|
677
|
+
sessionId: null,
|
|
678
|
+
sessionUrl: null,
|
|
679
|
+
businessCheck: { ...EMPTY_BUSINESS_CHECK },
|
|
374
680
|
questionnaireAnswers: {},
|
|
375
681
|
contact: {},
|
|
376
682
|
contactChallenge: null,
|
|
@@ -383,6 +689,11 @@ export function createKycStore(
|
|
|
383
689
|
chipData: null,
|
|
384
690
|
poaDocumentType: null,
|
|
385
691
|
poaFileName: null,
|
|
692
|
+
address: null,
|
|
693
|
+
addressPhotoPreview: null,
|
|
694
|
+
addressIntroSeen: false,
|
|
695
|
+
addressEntranceFraming: false,
|
|
696
|
+
addressSandboxOutcome: null,
|
|
386
697
|
isLoading: false,
|
|
387
698
|
error: null,
|
|
388
699
|
});
|
|
@@ -395,7 +706,9 @@ export function createKycStore(
|
|
|
395
706
|
// collapses consecutive duplicates). Rides the submission as
|
|
396
707
|
// metadata.device.stepLog for the dashboard timeline.
|
|
397
708
|
store.subscribe((s, prev) => {
|
|
398
|
-
if (s.currentStep !== prev.currentStep)
|
|
709
|
+
if (s.currentStep !== prev.currentStep) {
|
|
710
|
+
recordStep(s.currentStep, multiIdSlotOf(s), selectedIdTypeOf(s));
|
|
711
|
+
}
|
|
399
712
|
});
|
|
400
713
|
|
|
401
714
|
// A fresh store IS a fresh session: the runtime provider creates one per
|
|
@@ -404,7 +717,30 @@ export function createKycStore(
|
|
|
404
717
|
// subscription above only fires on CHANGE, and a new store already sits on
|
|
405
718
|
// 'consent').
|
|
406
719
|
resetStepLog();
|
|
407
|
-
|
|
720
|
+
// The module-level GPS cache and the auto-locate latch are scoped to ONE
|
|
721
|
+
// verification, and this — not reset() — is where a verification begins.
|
|
722
|
+
// Clearing them only in reset() left both alive across modal launches, so a
|
|
723
|
+
// second verification in the same app session dropped its pin wherever the
|
|
724
|
+
// phone had been on the first (possibly hours and a city away) and skipped
|
|
725
|
+
// the automatic locate entirely, since the latch was already spent.
|
|
726
|
+
resetCurrentFix();
|
|
727
|
+
resetAutoLocate();
|
|
728
|
+
// A workflow that switched the consent screen off opens on its first real
|
|
729
|
+
// step; the literal 'consent' above is only the shape before the config is
|
|
730
|
+
// consulted. Set BEFORE the journey is recorded, so the log opens where the
|
|
731
|
+
// person does.
|
|
732
|
+
const opening = openingStep(store.getState());
|
|
733
|
+
if (opening !== store.getState().currentStep) store.setState({ currentStep: opening });
|
|
734
|
+
recordStep(
|
|
735
|
+
store.getState().currentStep,
|
|
736
|
+
multiIdSlotOf(store.getState()),
|
|
737
|
+
selectedIdTypeOf(store.getState()),
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
// The attempt session: minted at launch (a fresh store IS a fresh attempt),
|
|
741
|
+
// with progress written as the user moves. Both best-effort by contract.
|
|
742
|
+
startAttemptSession(store);
|
|
743
|
+
watchSessionProgress(store);
|
|
408
744
|
|
|
409
745
|
return store;
|
|
410
746
|
}
|
|
@@ -19,6 +19,26 @@ export interface ServerConfigState {
|
|
|
19
19
|
status: ServerConfigStatus;
|
|
20
20
|
idTypes: SdkConfigIdType[];
|
|
21
21
|
branding?: SdkConfigBranding;
|
|
22
|
+
/** The visitor's country from their IP — a default, never evidence. */
|
|
23
|
+
geoCountry?: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the platform has a forward-search backend, and which one. Absent
|
|
26
|
+
* means the address flow simply has no search screen: the applicant places
|
|
27
|
+
* the pin by hand, which is the fallback every address failure degrades to.
|
|
28
|
+
*
|
|
29
|
+
* The Google KEY itself never rides here (it is the hosted page's alone);
|
|
30
|
+
* the framed picker reaches this SDK as a URL instead — see mapsFrameUrl.
|
|
31
|
+
* Street View entrance framing still does not exist on mobile.
|
|
32
|
+
*/
|
|
33
|
+
addressSearch?: boolean;
|
|
34
|
+
addressSearchMode?: 'autocomplete' | 'basic';
|
|
35
|
+
/**
|
|
36
|
+
* The framed Google-map picker page for a WebView (`react-native-webview`,
|
|
37
|
+
* an optional peer): our hosted /embed/map plus a signed APP grant. Null or
|
|
38
|
+
* absent ⇒ the built-in OSM picker, which is also the fallback when the
|
|
39
|
+
* page never reports ready.
|
|
40
|
+
*/
|
|
41
|
+
mapsFrameUrl?: string | null;
|
|
22
42
|
environment?: 'DEVELOPMENT' | 'SANDBOX' | 'PRODUCTION';
|
|
23
43
|
/** HTTP status of a failed config fetch (if any). */
|
|
24
44
|
statusCode?: number;
|