@smileid/usesmileid 12.0.1 → 12.0.2
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/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/dist/camera/camera_defaults.d.ts +2 -0
- package/dist/camera/camera_defaults.d.ts.map +1 -1
- package/dist/camera/camera_defaults.js +2 -0
- package/dist/camera/camera_defaults.js.map +1 -1
- package/dist/core/exception/flow_exceptions.d.ts +4 -2
- package/dist/core/exception/flow_exceptions.d.ts.map +1 -1
- package/dist/core/exception/flow_exceptions.js +3 -2
- package/dist/core/exception/flow_exceptions.js.map +1 -1
- package/dist/core/exception/missing_job_type_exception.d.ts +1 -3
- package/dist/core/exception/missing_job_type_exception.d.ts.map +1 -1
- package/dist/core/exception/missing_job_type_exception.js +2 -4
- package/dist/core/exception/missing_job_type_exception.js.map +1 -1
- package/dist/data/api/interceptors/smile_auth_interceptor.js +1 -1
- package/dist/data/api/interceptors/smile_auth_interceptor.js.map +1 -1
- package/dist/data/api/smile_id_api.d.ts +4 -0
- package/dist/data/api/smile_id_api.d.ts.map +1 -1
- package/dist/data/api/smile_id_api.js +30 -27
- package/dist/data/api/smile_id_api.js.map +1 -1
- package/dist/data/model/job_request.d.ts +28 -12
- package/dist/data/model/job_request.d.ts.map +1 -1
- package/dist/data/model/token_payload.d.ts +78 -0
- package/dist/data/model/token_payload.d.ts.map +1 -0
- package/dist/data/model/token_payload.js +132 -0
- package/dist/data/model/token_payload.js.map +1 -0
- package/dist/data/model/user_details_payload.d.ts +39 -0
- package/dist/data/model/user_details_payload.d.ts.map +1 -0
- package/dist/data/model/user_details_payload.js +53 -0
- package/dist/data/model/user_details_payload.js.map +1 -0
- package/dist/data/util/use_smile_id_token_decoder.d.ts +15 -3
- package/dist/data/util/use_smile_id_token_decoder.d.ts.map +1 -1
- package/dist/data/util/use_smile_id_token_decoder.js +20 -8
- package/dist/data/util/use_smile_id_token_decoder.js.map +1 -1
- package/dist/l10n/strings_en.d.ts +4 -1
- package/dist/l10n/strings_en.d.ts.map +1 -1
- package/dist/l10n/strings_en.js +4 -1
- package/dist/l10n/strings_en.js.map +1 -1
- package/dist/l10n/use_smile_id_localizations.d.ts +10 -0
- package/dist/l10n/use_smile_id_localizations.d.ts.map +1 -1
- package/dist/l10n/use_smile_id_localizations.js +59 -3
- package/dist/l10n/use_smile_id_localizations.js.map +1 -1
- package/dist/metadata/metadata_configuration_mappers.js +1 -1
- package/dist/metadata/metadata_configuration_mappers.js.map +1 -1
- package/dist/metadata/use_smile_id_metadata_factory.js +1 -1
- package/dist/metadata/use_smile_id_metadata_factory.js.map +1 -1
- package/dist/presentation/components/use_smile_id_button.d.ts.map +1 -1
- package/dist/presentation/components/use_smile_id_button.js +8 -2
- package/dist/presentation/components/use_smile_id_button.js.map +1 -1
- package/dist/presentation/flow/dsl/builder/screens_builder.d.ts +3 -6
- package/dist/presentation/flow/dsl/builder/screens_builder.d.ts.map +1 -1
- package/dist/presentation/flow/dsl/builder/screens_builder.js +4 -6
- package/dist/presentation/flow/dsl/builder/screens_builder.js.map +1 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.d.ts.map +1 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js +23 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js.map +1 -1
- package/dist/presentation/flow/flow_navigation_manager.d.ts +17 -2
- package/dist/presentation/flow/flow_navigation_manager.d.ts.map +1 -1
- package/dist/presentation/flow/flow_navigation_manager.js +77 -45
- package/dist/presentation/flow/flow_navigation_manager.js.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_processing_screen.d.ts.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_processing_screen.js +25 -5
- package/dist/presentation/flow/screen/orchestrated_processing_screen.js.map +1 -1
- package/dist/presentation/flow/use_smile_id_flow.d.ts.map +1 -1
- package/dist/presentation/flow/use_smile_id_flow.js +35 -8
- package/dist/presentation/flow/use_smile_id_flow.js.map +1 -1
- package/dist/presentation/flow/validation/flow_validator.d.ts +16 -2
- package/dist/presentation/flow/validation/flow_validator.d.ts.map +1 -1
- package/dist/presentation/flow/validation/flow_validator.js +19 -27
- package/dist/presentation/flow/validation/flow_validator.js.map +1 -1
- package/dist/presentation/flow/validation/job_type_validator.d.ts +7 -0
- package/dist/presentation/flow/validation/job_type_validator.d.ts.map +1 -1
- package/dist/presentation/flow/validation/job_type_validator.js +52 -27
- package/dist/presentation/flow/validation/job_type_validator.js.map +1 -1
- package/dist/presentation/flow/validation/user_details_validator.d.ts +9 -0
- package/dist/presentation/flow/validation/user_details_validator.d.ts.map +1 -0
- package/dist/presentation/flow/validation/user_details_validator.js +40 -0
- package/dist/presentation/flow/validation/user_details_validator.js.map +1 -0
- package/dist/presentation/screen/helpers/capture_screen_helpers.d.ts.map +1 -1
- package/dist/presentation/screen/helpers/capture_screen_helpers.js +1 -0
- package/dist/presentation/screen/helpers/capture_screen_helpers.js.map +1 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.d.ts.map +1 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.js +64 -18
- package/dist/presentation/screen/use_smile_id_capture_screen.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/react_native_mock.tsx +22 -0
- package/src/__tests__/snapshots/__snapshots__/screens_structural.test.tsx.snap +14 -8
- package/src/camera/__tests__/camera_permissions.test.ts +72 -0
- package/src/camera/__tests__/camera_system_focus.test.ts +173 -0
- package/src/camera/__tests__/use_smile_id_camera_preview.test.tsx +461 -0
- package/src/camera/__tests__/use_smile_id_frame_stream_hook.test.tsx +229 -0
- package/src/camera/camera_defaults.ts +3 -0
- package/src/core/exception/__tests__/builder_validation_exceptions.test.ts +14 -0
- package/src/core/exception/flow_exceptions.test.ts +10 -0
- package/src/core/exception/flow_exceptions.ts +3 -2
- package/src/core/exception/missing_job_type_exception.ts +2 -4
- package/src/data/api/__tests__/smile_id_api.test.ts +275 -1
- package/src/data/api/interceptors/__tests__/credential_header_redaction.test.ts +144 -0
- package/src/data/api/interceptors/smile_auth_interceptor.ts +1 -1
- package/src/data/api/smile_id_api.ts +36 -49
- package/src/data/dsl/__tests__/use_smile_id_network_builder.test.ts +157 -0
- package/src/data/factory/__tests__/signed_job_submission_headers.test.ts +1 -1
- package/src/data/model/__tests__/token_payload.test.ts +224 -0
- package/src/data/model/__tests__/user_details_payload.test.ts +100 -0
- package/src/data/model/job_request.ts +27 -12
- package/src/data/model/token_payload.ts +182 -0
- package/src/data/model/user_details_payload.ts +84 -0
- package/src/data/repository/__tests__/network_retries.test.ts +1 -1
- package/src/data/repository/__tests__/repository_dispatch.test.ts +366 -0
- package/src/data/repository/__tests__/use_smile_id_repository.test.ts +1 -1
- package/src/data/security/__tests__/signing_crypto.test.ts +203 -0
- package/src/data/util/__tests__/use_smile_id_token_decoder.test.ts +179 -0
- package/src/data/util/use_smile_id_token_decoder.ts +29 -8
- package/src/l10n/__tests__/use_smile_id_localizations.test.ts +122 -5
- package/src/l10n/__tests__/use_smile_id_localizations_provider.test.tsx +112 -0
- package/src/l10n/__tests__/use_smile_id_localizations_surface.test.ts +231 -0
- package/src/l10n/strings_en.ts +5 -1
- package/src/l10n/use_smile_id_localizations.ts +80 -3
- package/src/metadata/__tests__/device_orientation_sampling.test.ts +165 -0
- package/src/metadata/__tests__/geolocation.test.ts +150 -0
- package/src/metadata/__tests__/metadata_configuration_objects.test.ts +1 -1
- package/src/metadata/__tests__/metadata_device_context.test.ts +3 -3
- package/src/metadata/__tests__/metadata_golden_fixture.json +1 -1
- package/src/metadata/__tests__/metadata_payload_golden.test.ts +4 -4
- package/src/metadata/__tests__/optional_native_modules.test.ts +234 -0
- package/src/metadata/__tests__/use_smile_id_metadata.test.ts +1 -1
- package/src/metadata/__tests__/use_smile_id_metadata_factory.test.ts +615 -0
- package/src/metadata/metadata_configuration_mappers.ts +1 -1
- package/src/metadata/use_smile_id_metadata_factory.ts +1 -1
- package/src/presentation/components/__tests__/use_smile_id_content_card.test.tsx +112 -0
- package/src/presentation/components/__tests__/use_smile_id_face_oval_image.test.tsx +190 -0
- package/src/presentation/components/__tests__/use_smile_id_learn_more_content.test.tsx +159 -0
- package/src/presentation/components/__tests__/use_smile_id_processing_center_indicator.test.tsx +88 -0
- package/src/presentation/components/use_smile_id_button.tsx +7 -2
- package/src/presentation/flow/__tests__/flow_navigation_manager.test.ts +323 -1
- package/src/presentation/flow/__tests__/use_smile_id_flow.test.tsx +651 -0
- package/src/presentation/flow/dsl/__tests__/use_smile_id_builder_component.test.tsx +120 -0
- package/src/presentation/flow/dsl/builder/screens_builder.ts +4 -6
- package/src/presentation/flow/dsl/use_smile_id_flow_builder.ts +27 -1
- package/src/presentation/flow/flow_navigation_manager.ts +102 -92
- package/src/presentation/flow/graph/__tests__/screen_renderer.test.tsx +102 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_capture_screen.test.tsx +836 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_consent_screen.test.tsx +165 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_instructions_screen.test.tsx +163 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_preview_screen.test.tsx +224 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_processing_screen.test.tsx +338 -0
- package/src/presentation/flow/screen/orchestrated_processing_screen.tsx +42 -5
- package/src/presentation/flow/use_smile_id_flow.tsx +52 -8
- package/src/presentation/flow/validation/__tests__/flow_validator.test.ts +158 -0
- package/src/presentation/flow/validation/__tests__/job_type_validator.test.ts +200 -0
- package/src/presentation/flow/validation/flow_validator.ts +30 -31
- package/src/presentation/flow/validation/job_type_validator.ts +73 -39
- package/src/presentation/flow/validation/user_details_validator.ts +53 -0
- package/src/presentation/screen/__tests__/feedback_copy_table.test.ts +145 -0
- package/src/presentation/screen/__tests__/use_smile_id_capture_screen.test.tsx +348 -0
- package/src/presentation/screen/__tests__/use_smile_id_document_back_instructions.test.tsx +3 -1
- package/src/presentation/screen/__tests__/use_smile_id_permission_screen.test.tsx +68 -0
- package/src/presentation/screen/helpers/capture_screen_helpers.ts +1 -0
- package/src/presentation/screen/use_smile_id_capture_screen.tsx +69 -18
- package/src/presentation/theme/__tests__/use_use_smile_id_fonts.test.tsx +166 -0
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
InvalidFieldValueException,
|
|
20
20
|
InvalidJobTypeScreenConfigException,
|
|
21
|
+
MissingJobTypeException,
|
|
21
22
|
MissingMLConfigurationBlockException,
|
|
22
23
|
} from "../../../../core/exception/job_validation_exceptions";
|
|
23
24
|
import {
|
|
@@ -25,9 +26,13 @@ import {
|
|
|
25
26
|
InvalidDocumentCaptureConfigException,
|
|
26
27
|
} from "../../../../core/exception/capture_validation_exceptions";
|
|
27
28
|
import {
|
|
29
|
+
runConsentValidation,
|
|
28
30
|
runDocumentVerificationParamsValidation,
|
|
29
31
|
runEnhancedDocumentVerificationParamsValidation,
|
|
32
|
+
runUserDetailsValidation,
|
|
30
33
|
} from "../flow_validator";
|
|
34
|
+
import { InvalidConsentInformationException } from "../../../../core/exception/consent_validation_exceptions";
|
|
35
|
+
import type { TokenPayload } from "../../../../data/model/token_payload";
|
|
31
36
|
|
|
32
37
|
function stubFaceAnalyzer(): FaceAnalyzer {
|
|
33
38
|
return {
|
|
@@ -456,3 +461,156 @@ describe("FlowValidator — document verification params", () => {
|
|
|
456
461
|
).toHaveLength(0);
|
|
457
462
|
});
|
|
458
463
|
});
|
|
464
|
+
|
|
465
|
+
describe("FlowValidator — jobType recognition", () => {
|
|
466
|
+
const ml: MLConfig = { analyzers: { selfie: stubFaceAnalyzer() } };
|
|
467
|
+
|
|
468
|
+
function jobTypeIssue(jobType: JobType) {
|
|
469
|
+
const issues = runFlowValidation({
|
|
470
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
471
|
+
ml,
|
|
472
|
+
network: validNetwork(jobType),
|
|
473
|
+
userDetails: validUserDetails,
|
|
474
|
+
});
|
|
475
|
+
return issues.find(
|
|
476
|
+
(i): i is MissingJobTypeException => i instanceof MissingJobTypeException,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
it("rejects a jobType that is not one of the SDK's job types", () => {
|
|
481
|
+
expect(jobTypeIssue(99 as JobType)).toBeInstanceOf(MissingJobTypeException);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("still rejects the unknown sentinel", () => {
|
|
485
|
+
expect(jobTypeIssue(JobType.unknown)).toBeInstanceOf(
|
|
486
|
+
MissingJobTypeException,
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("accepts every job type the SDK submits", () => {
|
|
491
|
+
for (const jobType of Object.values(JobType)) {
|
|
492
|
+
if (jobType === JobType.unknown) continue;
|
|
493
|
+
expect(jobTypeIssue(jobType)).toBeUndefined();
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe("FlowValidator — token-provided user details (field-level relaxation)", () => {
|
|
499
|
+
function tokenPayload(overrides: Partial<TokenPayload> = {}): TokenPayload {
|
|
500
|
+
return {
|
|
501
|
+
hasGivenNames: false,
|
|
502
|
+
hasLastName: false,
|
|
503
|
+
hasEmail: false,
|
|
504
|
+
hasPhoneNumber: false,
|
|
505
|
+
...overrides,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
it("accepts contact-only userDetails when the token binds both names", () => {
|
|
510
|
+
const issues = runUserDetailsValidation(
|
|
511
|
+
{ givenNames: "", lastName: "", email: "ada@example.com" },
|
|
512
|
+
tokenPayload({ hasGivenNames: true, hasLastName: true }),
|
|
513
|
+
);
|
|
514
|
+
expect(issues).toHaveLength(0);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it("accepts names-only userDetails when the token binds a contact field", () => {
|
|
518
|
+
const emailBound = runUserDetailsValidation(
|
|
519
|
+
{ givenNames: "Ada", lastName: "Lovelace" },
|
|
520
|
+
tokenPayload({ hasEmail: true }),
|
|
521
|
+
);
|
|
522
|
+
expect(emailBound).toHaveLength(0);
|
|
523
|
+
|
|
524
|
+
const phoneBound = runUserDetailsValidation(
|
|
525
|
+
{ givenNames: "Ada", lastName: "Lovelace" },
|
|
526
|
+
tokenPayload({ hasPhoneNumber: true }),
|
|
527
|
+
);
|
|
528
|
+
expect(phoneBound).toHaveLength(0);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("relaxes per-field — a name the token does not bind still errors with today's message", () => {
|
|
532
|
+
const issues = runUserDetailsValidation(
|
|
533
|
+
{ givenNames: "", lastName: "", email: "ada@example.com" },
|
|
534
|
+
tokenPayload({ hasGivenNames: true }),
|
|
535
|
+
);
|
|
536
|
+
expect(issues).toHaveLength(1);
|
|
537
|
+
expect(issues[0]).toBeInstanceOf(InvalidFieldValueException);
|
|
538
|
+
expect(issues[0]?.message).toContain("userDetails.lastName");
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("regression: without a token payload every existing rule fires unchanged", () => {
|
|
542
|
+
const issues = runUserDetailsValidation({ givenNames: "", lastName: "" });
|
|
543
|
+
expect(issues).toHaveLength(3);
|
|
544
|
+
const messages = issues.map((i) => i.message).join("\n");
|
|
545
|
+
expect(messages).toContain("userDetails.givenNames");
|
|
546
|
+
expect(messages).toContain("userDetails.lastName");
|
|
547
|
+
expect(messages).toContain(
|
|
548
|
+
"at least one of email or phoneNumber must be non blank",
|
|
549
|
+
);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("threads tokenPayload through runFlowValidation to the field rules", () => {
|
|
553
|
+
const issues = runFlowValidation({
|
|
554
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
555
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
556
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
557
|
+
userDetails: { givenNames: "", lastName: "", email: "ada@example.com" },
|
|
558
|
+
consentInformation: {
|
|
559
|
+
granted: true,
|
|
560
|
+
grantedAt: "2026-01-01T00:00:00.000Z",
|
|
561
|
+
noticeLanguage: "EN",
|
|
562
|
+
noticePrivacyPolicyUrl: "https://example.com",
|
|
563
|
+
},
|
|
564
|
+
tokenPayload: tokenPayload({ hasGivenNames: true, hasLastName: true }),
|
|
565
|
+
});
|
|
566
|
+
expect(issues.some((i) => i instanceof InvalidFieldValueException)).toBe(
|
|
567
|
+
false,
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("a complete token consent skips content validation of the ignored preset", () => {
|
|
572
|
+
// Token data always wins: the preset is never sent, so its
|
|
573
|
+
// contents raise no issue even when they would fail on their own.
|
|
574
|
+
const issues = runFlowValidation({
|
|
575
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
576
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
577
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
578
|
+
userDetails: validUserDetails,
|
|
579
|
+
consentInformation: {
|
|
580
|
+
granted: false,
|
|
581
|
+
grantedAt: "",
|
|
582
|
+
noticeLanguage: "",
|
|
583
|
+
noticePrivacyPolicyUrl: "",
|
|
584
|
+
},
|
|
585
|
+
tokenPayload: {
|
|
586
|
+
...tokenPayload(),
|
|
587
|
+
consent: {
|
|
588
|
+
granted: true,
|
|
589
|
+
grantedAt: "2026-01-01T09:00:00.000Z",
|
|
590
|
+
noticeLanguage: "EN",
|
|
591
|
+
noticePrivacyPolicyUrl: "https://partner.test/privacy",
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
expect(
|
|
596
|
+
issues.some((i) => i instanceof InvalidConsentInformationException),
|
|
597
|
+
).toBe(false);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it("the standalone pre-flight validator stays strict (no token context)", () => {
|
|
601
|
+
// `b.validateUserDetails(payload)` has no token context by design —
|
|
602
|
+
// partners pre-flight the payload they would assign, and blank
|
|
603
|
+
// names still fail there.
|
|
604
|
+
expect(
|
|
605
|
+
runUserDetailsValidation({ givenNames: "", lastName: "Lovelace" }),
|
|
606
|
+
).not.toHaveLength(0);
|
|
607
|
+
expect(
|
|
608
|
+
runConsentValidation({
|
|
609
|
+
granted: false,
|
|
610
|
+
grantedAt: "",
|
|
611
|
+
noticeLanguage: "",
|
|
612
|
+
noticePrivacyPolicyUrl: "",
|
|
613
|
+
}),
|
|
614
|
+
).not.toHaveLength(0);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
@@ -20,10 +20,15 @@ import {
|
|
|
20
20
|
PreviewConfigBuilder,
|
|
21
21
|
} from "../../dsl/builder/screens_builder";
|
|
22
22
|
import { InvalidJobTypeScreenConfigException } from "../../../../core/exception/job_validation_exceptions";
|
|
23
|
+
import { InvalidFieldValueException } from "../../../../core/exception/invalid_field_value_exception";
|
|
23
24
|
import {
|
|
24
25
|
InvalidScreenOrderException,
|
|
25
26
|
MissingCaptureForPreviewException,
|
|
26
27
|
} from "../../../../core/exception/screen_config_validation_exceptions";
|
|
28
|
+
import type {
|
|
29
|
+
TokenConsent,
|
|
30
|
+
TokenPayload,
|
|
31
|
+
} from "../../../../data/model/token_payload";
|
|
27
32
|
|
|
28
33
|
function stubFaceAnalyzer(): FaceAnalyzer {
|
|
29
34
|
return {
|
|
@@ -733,6 +738,201 @@ describe("DocumentJobTypeValidator — Android-parity rules", () => {
|
|
|
733
738
|
});
|
|
734
739
|
});
|
|
735
740
|
|
|
741
|
+
describe("JobTypeValidator — token-provided user details & consent", () => {
|
|
742
|
+
function tokenPayload(overrides: Partial<TokenPayload> = {}): TokenPayload {
|
|
743
|
+
return {
|
|
744
|
+
hasGivenNames: false,
|
|
745
|
+
hasLastName: false,
|
|
746
|
+
hasEmail: false,
|
|
747
|
+
hasPhoneNumber: false,
|
|
748
|
+
...overrides,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const fullBinding = tokenPayload({
|
|
753
|
+
hasGivenNames: true,
|
|
754
|
+
hasLastName: true,
|
|
755
|
+
hasEmail: true,
|
|
756
|
+
hasPhoneNumber: true,
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
const completeTokenConsent: TokenConsent = {
|
|
760
|
+
granted: true,
|
|
761
|
+
grantedAt: "2026-01-01T09:00:00.000Z",
|
|
762
|
+
noticeLanguage: "EN",
|
|
763
|
+
noticePrivacyPolicyUrl: "https://partner.test/privacy",
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const validConsentInformation = {
|
|
767
|
+
granted: true,
|
|
768
|
+
grantedAt: "2024-01-01T00:00:00.000Z",
|
|
769
|
+
noticeLanguage: "EN",
|
|
770
|
+
noticePrivacyPolicyUrl: "https://example.com",
|
|
771
|
+
} as const;
|
|
772
|
+
|
|
773
|
+
// Steps + params that make each submitting job type otherwise valid,
|
|
774
|
+
// so the only variable under test is the userDetails / consent rule.
|
|
775
|
+
function validInputFor(jobType: JobType): JobTypeValidatorInput {
|
|
776
|
+
switch (jobType) {
|
|
777
|
+
case JobType.documentVerification:
|
|
778
|
+
case JobType.enhancedDocumentVerification:
|
|
779
|
+
return {
|
|
780
|
+
steps: [
|
|
781
|
+
consentStep(),
|
|
782
|
+
captureStep(CaptureType.selfie),
|
|
783
|
+
captureStep(CaptureType.document),
|
|
784
|
+
processingStep(),
|
|
785
|
+
],
|
|
786
|
+
network: validNetwork(jobType),
|
|
787
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
788
|
+
documentVerificationParams: validDocumentParams,
|
|
789
|
+
enhancedDocumentVerificationParams: validEnhancedDocumentParams,
|
|
790
|
+
};
|
|
791
|
+
case JobType.enhancedKyc:
|
|
792
|
+
return {
|
|
793
|
+
steps: [consentStep(), processingStep()],
|
|
794
|
+
network: validNetwork(jobType),
|
|
795
|
+
enhancedKYCParams: {
|
|
796
|
+
idType: "NATIONAL_ID",
|
|
797
|
+
idNumber: "1",
|
|
798
|
+
country: "KE",
|
|
799
|
+
},
|
|
800
|
+
};
|
|
801
|
+
default:
|
|
802
|
+
return {
|
|
803
|
+
steps: [
|
|
804
|
+
consentStep(),
|
|
805
|
+
captureStep(CaptureType.selfie),
|
|
806
|
+
processingStep(),
|
|
807
|
+
],
|
|
808
|
+
network: validNetwork(jobType),
|
|
809
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
810
|
+
biometricKYCParams: validBiometricParams,
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const USER_DETAILS_JOB_TYPES: readonly JobType[] = [
|
|
816
|
+
JobType.smartSelfieEnrollment,
|
|
817
|
+
JobType.smartSelfieAuthentication,
|
|
818
|
+
JobType.biometricKyc,
|
|
819
|
+
JobType.documentVerification,
|
|
820
|
+
JobType.enhancedDocumentVerification,
|
|
821
|
+
JobType.enhancedKyc,
|
|
822
|
+
];
|
|
823
|
+
|
|
824
|
+
for (const jobType of USER_DETAILS_JOB_TYPES) {
|
|
825
|
+
it(`${jobType}: userDetails may be omitted when the token binds names + a contact field`, () => {
|
|
826
|
+
const issues = run({
|
|
827
|
+
...validInputFor(jobType),
|
|
828
|
+
tokenPayload: fullBinding,
|
|
829
|
+
});
|
|
830
|
+
expect(
|
|
831
|
+
issues.some(
|
|
832
|
+
(i) =>
|
|
833
|
+
i instanceof InvalidJobTypeScreenConfigException &&
|
|
834
|
+
i.message.includes("userDetails is required"),
|
|
835
|
+
),
|
|
836
|
+
).toBe(false);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
it("a names-only token binding reports the missing contact field by name", () => {
|
|
841
|
+
const issues = run({
|
|
842
|
+
...validInputFor(JobType.smartSelfieEnrollment),
|
|
843
|
+
tokenPayload: tokenPayload({ hasGivenNames: true, hasLastName: true }),
|
|
844
|
+
});
|
|
845
|
+
expect(
|
|
846
|
+
issues.some(
|
|
847
|
+
(i) =>
|
|
848
|
+
i instanceof InvalidFieldValueException &&
|
|
849
|
+
i.message.includes("userDetails") &&
|
|
850
|
+
i.message.includes(
|
|
851
|
+
"at least one contact field must be bound by the token or supplied on the builder",
|
|
852
|
+
),
|
|
853
|
+
),
|
|
854
|
+
).toBe(true);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("complete token consent satisfies the consent-source rule with no local source", () => {
|
|
858
|
+
const issues = run({
|
|
859
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
860
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
861
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
862
|
+
userDetails: validUserDetails,
|
|
863
|
+
tokenPayload: { ...tokenPayload(), consent: completeTokenConsent },
|
|
864
|
+
});
|
|
865
|
+
expect(
|
|
866
|
+
issues.some(
|
|
867
|
+
(i) =>
|
|
868
|
+
i instanceof InvalidJobTypeScreenConfigException &&
|
|
869
|
+
i.message.includes(
|
|
870
|
+
"must include either a Consent screen or a pre-supplied consentInformation",
|
|
871
|
+
),
|
|
872
|
+
),
|
|
873
|
+
).toBe(false);
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it("a partial token consent fails validation naming the missing subfields", () => {
|
|
877
|
+
const issues = run({
|
|
878
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
879
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
880
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
881
|
+
userDetails: validUserDetails,
|
|
882
|
+
tokenPayload: {
|
|
883
|
+
...tokenPayload(),
|
|
884
|
+
consent: {
|
|
885
|
+
granted: true,
|
|
886
|
+
grantedAt: "2026-01-01T09:00:00.000Z",
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
expect(
|
|
891
|
+
issues.some(
|
|
892
|
+
(i) =>
|
|
893
|
+
i instanceof InvalidJobTypeScreenConfigException &&
|
|
894
|
+
i.message.includes(
|
|
895
|
+
"the token's consent binding is incomplete — missing: notice_language, notice_privacy_policy_url",
|
|
896
|
+
),
|
|
897
|
+
),
|
|
898
|
+
).toBe(true);
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
it("complete token consent alongside a consentInformation preset is allowed", () => {
|
|
902
|
+
const issues = run({
|
|
903
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
904
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
905
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
906
|
+
userDetails: validUserDetails,
|
|
907
|
+
consentInformation: validConsentInformation,
|
|
908
|
+
tokenPayload: { ...tokenPayload(), consent: completeTokenConsent },
|
|
909
|
+
});
|
|
910
|
+
expect(
|
|
911
|
+
issues.some((i) => i instanceof InvalidJobTypeScreenConfigException),
|
|
912
|
+
).toBe(false);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
it("the consent-source mutex still fires with complete token consent", () => {
|
|
916
|
+
const issues = run({
|
|
917
|
+
steps: [consentStep(), captureStep(CaptureType.selfie), processingStep()],
|
|
918
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
919
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
920
|
+
userDetails: validUserDetails,
|
|
921
|
+
consentInformation: validConsentInformation,
|
|
922
|
+
tokenPayload: { ...tokenPayload(), consent: completeTokenConsent },
|
|
923
|
+
});
|
|
924
|
+
expect(
|
|
925
|
+
issues.some(
|
|
926
|
+
(i) =>
|
|
927
|
+
i instanceof InvalidJobTypeScreenConfigException &&
|
|
928
|
+
i.message.includes(
|
|
929
|
+
"must not include both a Consent screen and a pre-supplied consentInformation",
|
|
930
|
+
),
|
|
931
|
+
),
|
|
932
|
+
).toBe(true);
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
736
936
|
describe("NonCaptureJobTypeValidator — Android-parity rules", () => {
|
|
737
937
|
it("passes a fully-configured EnhancedKyc flow", () => {
|
|
738
938
|
const issues = run({
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
EnhancedKYCParams,
|
|
10
10
|
UserDetails,
|
|
11
11
|
} from "../../../core/models/params";
|
|
12
|
-
import { JobType } from "../../../core/models/job_type";
|
|
12
|
+
import { JobType, jobTypeFromValue } from "../../../core/models/job_type";
|
|
13
13
|
import type {
|
|
14
14
|
DocumentCaptureConfigBuilder,
|
|
15
15
|
ScreenSpec,
|
|
@@ -34,8 +34,10 @@ import {
|
|
|
34
34
|
InvalidMissingCaptureTypeException,
|
|
35
35
|
} from "../../../core/exception/capture_validation_exceptions";
|
|
36
36
|
import { EmptyScreensBlockException } from "../../../core/exception/screen_config_validation_exceptions";
|
|
37
|
+
import type { TokenPayload } from "../../../data/model/token_payload";
|
|
37
38
|
import { ValidationState, type ValidationIssue } from "./validation_state";
|
|
38
39
|
import { collectJobTypeIssues } from "./job_type_validator";
|
|
40
|
+
import { missingUserDetailsFieldIssues } from "./user_details_validator";
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* Pure flow validator — returns typed `UseSmileIDValidationException`
|
|
@@ -72,6 +74,12 @@ type FlowValidatorInput = {
|
|
|
72
74
|
| EnhancedDocumentVerificationParams
|
|
73
75
|
| undefined;
|
|
74
76
|
readonly userDetails?: UserDetails | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* User-detail/consent bindings decoded from the JWT `payload` claim.
|
|
79
|
+
* `undefined` (no token, offline mode, legacy token) keeps every rule
|
|
80
|
+
* exactly as strict as before token support existed.
|
|
81
|
+
*/
|
|
82
|
+
readonly tokenPayload?: TokenPayload | undefined;
|
|
75
83
|
};
|
|
76
84
|
|
|
77
85
|
/**
|
|
@@ -185,9 +193,13 @@ export function runFlowValidation(
|
|
|
185
193
|
// build (ERROR) whenever it's missing — whether or not a network
|
|
186
194
|
// block was provided. The missing-network WARNING above still fires
|
|
187
195
|
// alongside this when the block is absent.
|
|
196
|
+
// `jobTypeFromValue` collapses the unknown sentinel and any out-of-enum value, so an unrecognised job_type_id fails the build too.
|
|
188
197
|
if (hasProcessingScreen) {
|
|
189
198
|
const jobType = input.network?.config?.jobType;
|
|
190
|
-
if (
|
|
199
|
+
if (
|
|
200
|
+
jobType === undefined ||
|
|
201
|
+
jobTypeFromValue(jobType) === JobType.unknown
|
|
202
|
+
) {
|
|
191
203
|
issues.push(new MissingJobTypeException());
|
|
192
204
|
}
|
|
193
205
|
}
|
|
@@ -238,7 +250,9 @@ export function runFlowValidation(
|
|
|
238
250
|
);
|
|
239
251
|
}
|
|
240
252
|
|
|
241
|
-
|
|
253
|
+
// A token consent binding supersedes the preset entirely (token data
|
|
254
|
+
// always wins), so the ignored preset's contents are not validated.
|
|
255
|
+
if (input.consentInformation && input.tokenPayload?.consent === undefined) {
|
|
242
256
|
issues.push(...runConsentValidation(input.consentInformation));
|
|
243
257
|
}
|
|
244
258
|
if (input.enhancedKYCParams) {
|
|
@@ -262,7 +276,9 @@ export function runFlowValidation(
|
|
|
262
276
|
);
|
|
263
277
|
}
|
|
264
278
|
if (input.userDetails) {
|
|
265
|
-
issues.push(
|
|
279
|
+
issues.push(
|
|
280
|
+
...runUserDetailsValidation(input.userDetails, input.tokenPayload),
|
|
281
|
+
);
|
|
266
282
|
}
|
|
267
283
|
|
|
268
284
|
// Every flow-composition rule (screen ordering, duplicate screens,
|
|
@@ -408,40 +424,23 @@ function runKYCParamsValidation(
|
|
|
408
424
|
* keep working unchanged after the consolidation away from nesting
|
|
409
425
|
* `userDetails` inside the KYC param shapes.
|
|
410
426
|
*
|
|
427
|
+
* Each field requirement is evaluated against the union of builder
|
|
428
|
+
* input and token bindings: a field the token binds (see
|
|
429
|
+
* `TokenPayload`) never has to be supplied locally — the server
|
|
430
|
+
* injects it, and the SDK drops the local value from the wire. With
|
|
431
|
+
* no `tokenPayload` the behavior is exactly the pre-token rule set.
|
|
432
|
+
*
|
|
411
433
|
* Exposed via `b.validateUserDetails(payload)` so partners can
|
|
412
434
|
* pre-flight a payload before assigning to `b.userDetails`, mirroring
|
|
413
435
|
* the existing `b.validateConsent` / `b.validateEnhancedKYCParams`
|
|
414
|
-
* pattern
|
|
436
|
+
* pattern (the pre-flight helper always validates strictly, without
|
|
437
|
+
* token context).
|
|
415
438
|
*/
|
|
416
439
|
export function runUserDetailsValidation(
|
|
417
440
|
userDetails: UserDetails,
|
|
441
|
+
tokenPayload?: TokenPayload,
|
|
418
442
|
): ValidationIssue[] {
|
|
419
|
-
|
|
420
|
-
for (const field of ["givenNames", "lastName"] as const) {
|
|
421
|
-
const value = userDetails[field];
|
|
422
|
-
if (!value || !value.trim()) {
|
|
423
|
-
issues.push(
|
|
424
|
-
new InvalidFieldValueException({
|
|
425
|
-
fieldName: `userDetails.${field}`,
|
|
426
|
-
reason: "must not be blank",
|
|
427
|
-
}),
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
// At-least-one-of contact rule: email and phoneNumber are each
|
|
432
|
-
// optional, but the partner must supply one of them.
|
|
433
|
-
const hasEmail = !!userDetails.email && userDetails.email.trim().length > 0;
|
|
434
|
-
const hasPhone =
|
|
435
|
-
!!userDetails.phoneNumber && userDetails.phoneNumber.trim().length > 0;
|
|
436
|
-
if (!hasEmail && !hasPhone) {
|
|
437
|
-
issues.push(
|
|
438
|
-
new InvalidFieldValueException({
|
|
439
|
-
fieldName: "userDetails",
|
|
440
|
-
reason: "at least one of email or phoneNumber must be non blank",
|
|
441
|
-
}),
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
return issues;
|
|
443
|
+
return missingUserDetailsFieldIssues(userDetails, tokenPayload);
|
|
445
444
|
}
|
|
446
445
|
|
|
447
446
|
/**
|
|
@@ -18,6 +18,12 @@ import {
|
|
|
18
18
|
MissingCaptureForPreviewException,
|
|
19
19
|
} from "../../../core/exception/screen_config_validation_exceptions";
|
|
20
20
|
import { IssueSeverity } from "../../../core/exception/issue_severity";
|
|
21
|
+
import {
|
|
22
|
+
tokenConsentMissingSubfields,
|
|
23
|
+
bindsRequiredUserDetails,
|
|
24
|
+
type TokenPayload,
|
|
25
|
+
} from "../../../data/model/token_payload";
|
|
26
|
+
import { missingUserDetailsFieldIssues } from "./user_details_validator";
|
|
21
27
|
import type { ValidationIssue } from "./validation_state";
|
|
22
28
|
|
|
23
29
|
/**
|
|
@@ -66,6 +72,12 @@ export type JobTypeValidatorInput = {
|
|
|
66
72
|
| EnhancedDocumentVerificationParams
|
|
67
73
|
| undefined;
|
|
68
74
|
readonly userDetails?: UserDetails | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* User-detail/consent bindings decoded from the JWT `payload` claim.
|
|
77
|
+
* `undefined` (no token, offline mode, legacy token) keeps every rule
|
|
78
|
+
* exactly as strict as before token support existed.
|
|
79
|
+
*/
|
|
80
|
+
readonly tokenPayload?: TokenPayload | undefined;
|
|
69
81
|
};
|
|
70
82
|
|
|
71
83
|
/**
|
|
@@ -93,9 +105,13 @@ function jobTypeName(jobType: JobType): string {
|
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
/**
|
|
96
|
-
* Consent rule:
|
|
97
|
-
*
|
|
98
|
-
*
|
|
108
|
+
* Consent rule: token consent always wins. A token that carries a
|
|
109
|
+
* consent binding must carry all four subfields — the local sources
|
|
110
|
+
* are then ignored (the consent screen is skipped and any preset is
|
|
111
|
+
* not sent), so an incomplete binding is a build-time error rather
|
|
112
|
+
* than a silent fallback. Without a token binding, exactly one local
|
|
113
|
+
* form of consent is required — either a Consent screen or
|
|
114
|
+
* pre-supplied `consentInformation`, never both.
|
|
99
115
|
*/
|
|
100
116
|
function appendConsentRule(
|
|
101
117
|
jobType: JobType,
|
|
@@ -104,6 +120,33 @@ function appendConsentRule(
|
|
|
104
120
|
): void {
|
|
105
121
|
const hasConsentScreen = input.steps.some((s) => s.kind === "consent");
|
|
106
122
|
const hasConsentInformation = input.consentInformation !== undefined;
|
|
123
|
+
if (hasConsentScreen && hasConsentInformation) {
|
|
124
|
+
issues.push(
|
|
125
|
+
InvalidJobTypeScreenConfigException.forJob({
|
|
126
|
+
jobType,
|
|
127
|
+
reason:
|
|
128
|
+
"must not include both a Consent screen and a pre-supplied consentInformation — use one or the other",
|
|
129
|
+
suggestedFix:
|
|
130
|
+
"Remove the consent { } screen if supplying consentInformation, or clear consentInformation if using the consent screen",
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const tokenConsent = input.tokenPayload?.consent;
|
|
136
|
+
if (tokenConsent !== undefined) {
|
|
137
|
+
const missing = tokenConsentMissingSubfields(tokenConsent);
|
|
138
|
+
if (missing.length > 0) {
|
|
139
|
+
issues.push(
|
|
140
|
+
InvalidJobTypeScreenConfigException.forJob({
|
|
141
|
+
jobType,
|
|
142
|
+
reason: `the token's consent binding is incomplete — missing: ${missing.join(", ")}`,
|
|
143
|
+
suggestedFix:
|
|
144
|
+
"Bind all four consent fields when minting the token at /v3/token, or bind none and collect consent in the app",
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
107
150
|
if (!hasConsentScreen && !hasConsentInformation) {
|
|
108
151
|
issues.push(
|
|
109
152
|
InvalidJobTypeScreenConfigException.forJob({
|
|
@@ -114,17 +157,36 @@ function appendConsentRule(
|
|
|
114
157
|
"Add a consent { } screen to your flow, or supply consentInformation via the builder",
|
|
115
158
|
}),
|
|
116
159
|
);
|
|
117
|
-
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* userDetails rule: the token and the builder fill the requirement
|
|
165
|
+
* together — token-bound fields (see `bindsRequiredUserDetails`) count
|
|
166
|
+
* as supplied and the builder must cover the rest. With no token
|
|
167
|
+
* binding and no builder value the legacy blanket error fires; with a
|
|
168
|
+
* partial token binding the per-field errors name exactly what neither
|
|
169
|
+
* side provided.
|
|
170
|
+
*/
|
|
171
|
+
function appendUserDetailsRequiredRule(
|
|
172
|
+
jobType: JobType,
|
|
173
|
+
input: JobTypeValidatorInput,
|
|
174
|
+
issues: ValidationIssue[],
|
|
175
|
+
): void {
|
|
176
|
+
if (input.userDetails !== undefined) return;
|
|
177
|
+
if (bindsRequiredUserDetails(input.tokenPayload)) return;
|
|
178
|
+
if (input.tokenPayload === undefined) {
|
|
118
179
|
issues.push(
|
|
119
180
|
InvalidJobTypeScreenConfigException.forJob({
|
|
120
181
|
jobType,
|
|
121
|
-
reason:
|
|
122
|
-
"must not include both a Consent screen and a pre-supplied consentInformation — use one or the other",
|
|
182
|
+
reason: `userDetails is required for ${jobTypeName(jobType)} jobs`,
|
|
123
183
|
suggestedFix:
|
|
124
|
-
"
|
|
184
|
+
"Set builder.userDetails with the end user PII before building",
|
|
125
185
|
}),
|
|
126
186
|
);
|
|
187
|
+
return;
|
|
127
188
|
}
|
|
189
|
+
issues.push(...missingUserDetailsFieldIssues(undefined, input.tokenPayload));
|
|
128
190
|
}
|
|
129
191
|
|
|
130
192
|
/**
|
|
@@ -381,16 +443,7 @@ export class SelfieJobTypeValidator implements JobTypeValidator {
|
|
|
381
443
|
);
|
|
382
444
|
}
|
|
383
445
|
|
|
384
|
-
|
|
385
|
-
issues.push(
|
|
386
|
-
InvalidJobTypeScreenConfigException.forJob({
|
|
387
|
-
jobType: this.jobType,
|
|
388
|
-
reason: `userDetails is required for ${jobTypeName(this.jobType)} jobs`,
|
|
389
|
-
suggestedFix:
|
|
390
|
-
"Set builder.userDetails with the end user PII before building",
|
|
391
|
-
}),
|
|
392
|
-
);
|
|
393
|
-
}
|
|
446
|
+
appendUserDetailsRequiredRule(this.jobType, input, issues);
|
|
394
447
|
}
|
|
395
448
|
}
|
|
396
449
|
|
|
@@ -477,16 +530,7 @@ export class DocumentJobTypeValidator implements JobTypeValidator {
|
|
|
477
530
|
);
|
|
478
531
|
}
|
|
479
532
|
|
|
480
|
-
|
|
481
|
-
issues.push(
|
|
482
|
-
InvalidJobTypeScreenConfigException.forJob({
|
|
483
|
-
jobType: this.jobType,
|
|
484
|
-
reason: `userDetails is required for ${jobTypeName(this.jobType)} jobs`,
|
|
485
|
-
suggestedFix:
|
|
486
|
-
"Set builder.userDetails with the end user PII before building",
|
|
487
|
-
}),
|
|
488
|
-
);
|
|
489
|
-
}
|
|
533
|
+
appendUserDetailsRequiredRule(this.jobType, input, issues);
|
|
490
534
|
}
|
|
491
535
|
|
|
492
536
|
/**
|
|
@@ -578,18 +622,8 @@ export class NonCaptureJobTypeValidator implements JobTypeValidator {
|
|
|
578
622
|
);
|
|
579
623
|
}
|
|
580
624
|
|
|
581
|
-
if (
|
|
582
|
-
this.jobType
|
|
583
|
-
input.userDetails === undefined
|
|
584
|
-
) {
|
|
585
|
-
issues.push(
|
|
586
|
-
InvalidJobTypeScreenConfigException.forJob({
|
|
587
|
-
jobType: this.jobType,
|
|
588
|
-
reason: "userDetails is required for EnhancedKyc jobs",
|
|
589
|
-
suggestedFix:
|
|
590
|
-
"Set builder.userDetails with the end user PII before building",
|
|
591
|
-
}),
|
|
592
|
-
);
|
|
625
|
+
if (this.jobType === JobType.enhancedKyc) {
|
|
626
|
+
appendUserDetailsRequiredRule(this.jobType, input, issues);
|
|
593
627
|
}
|
|
594
628
|
}
|
|
595
629
|
}
|