From 3ec2b9c0d47fac7680e4ef89e6222e2c4f8a0de8 Mon Sep 17 00:00:00 2001 From: joshuakrueger-dfx Date: Tue, 28 Jul 2026 12:14:14 +0200 Subject: [PATCH 1/4] feat(kyc): expose submission completeness on personal and financial data The API returns whether a submission was complete and which required fields are still missing. Type the two submit calls accordingly so consumers can surface the information, and model the previously anonymous question conditions as a named interface. --- packages/core/src/client/KycApi.ts | 9 +++++---- packages/core/src/definitions/index.ts | 2 ++ packages/core/src/definitions/kyc.ts | 22 ++++++++++++++++++---- packages/core/src/index.ts | 2 ++ packages/react/src/definitions/kyc.ts | 2 ++ packages/react/src/hooks/kyc.hook.ts | 9 +++++---- packages/react/src/index.ts | 2 ++ 7 files changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/core/src/client/KycApi.ts b/packages/core/src/client/KycApi.ts index f20f18e5..ea0979a9 100644 --- a/packages/core/src/client/KycApi.ts +++ b/packages/core/src/client/KycApi.ts @@ -3,6 +3,7 @@ import { KycInfo, KycStepSession, KycStepBase, + KycStepSubmit, KycStepName, KycStepType, KycContactData, @@ -75,8 +76,8 @@ export class KycApi { return this.kycRequest(code, { url, method: 'PUT', data }); } - async setPersonalData(code: string, url: string, data: KycPersonalData): Promise { - return this.kycRequest(code, { url, method: 'PUT', data }); + async setPersonalData(code: string, url: string, data: KycPersonalData): Promise { + return this.kycRequest(code, { url, method: 'PUT', data }); } async setManualIdentData(code: string, url: string, data: KycManualIdentData): Promise { @@ -120,8 +121,8 @@ export class KycApi { return this.kycRequest(code, { url: `${url}${query}`, method: 'GET' }); } - async setFinancialData(code: string, url: string, data: KycFinancialResponses): Promise { - return this.kycRequest(code, { url, method: 'PUT', data }); + async setFinancialData(code: string, url: string, data: KycFinancialResponses): Promise { + return this.kycRequest(code, { url, method: 'PUT', data }); } async setPaymentData(code: string, url: string, data: PaymentData): Promise { diff --git a/packages/core/src/definitions/index.ts b/packages/core/src/definitions/index.ts index 48e7e7f6..882a84ec 100644 --- a/packages/core/src/definitions/index.ts +++ b/packages/core/src/definitions/index.ts @@ -73,6 +73,7 @@ export type { KycStepBase, KycStep, KycStepSession, + KycStepSubmit, KycContactData, KycAddress, KycPersonalData, @@ -94,6 +95,7 @@ export type { KycFinancialResponse, KycFinancialResponses, KycFinancialOption, + KycFinancialCondition, KycFinancialQuestion, KycFinancialQuestions, TfaSetup, diff --git a/packages/core/src/definitions/kyc.ts b/packages/core/src/definitions/kyc.ts index 6fba2d66..0bcc713b 100644 --- a/packages/core/src/definitions/kyc.ts +++ b/packages/core/src/definitions/kyc.ts @@ -212,6 +212,17 @@ export interface KycStepSession extends KycStepBase { session?: KycSessionInfo; } +/** Response of KYC data submit endpoints (personal / financial). */ +export interface KycStepSubmit extends KycStepBase { + /** Whether the submission fulfilled all required fields and the step advanced. When false, a draft was saved but the step did not progress. */ + complete: boolean; + /** + * Missing required field paths (personal: e.g. `firstName`, `address.city`; financial: unanswered applicable question keys). + * Empty when `complete` is true. May also be empty while `complete` is false if the blocking field belongs to another step. + */ + missingFields: string[]; +} + // personal data export interface KycContactData { mail: string; @@ -441,16 +452,19 @@ export interface KycFinancialOption { text: string; } +/** Condition under which a financial question is applicable (previous answer match). */ +export interface KycFinancialCondition { + question: string; + response: string; +} + export interface KycFinancialQuestion { key: string; type: QuestionType; title: string; description: string; options?: KycFinancialOption[]; - conditions?: { - question: string; - response: string; - }[]; + conditions?: KycFinancialCondition[]; } export interface KycFinancialQuestions extends KycFinancialResponses { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5bcfe80d..aef4d893 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -165,6 +165,7 @@ export type { KycStepBase, KycStep, KycStepSession, + KycStepSubmit, KycContactData, KycAddress, KycPersonalData, @@ -186,6 +187,7 @@ export type { KycFinancialResponse, KycFinancialResponses, KycFinancialOption, + KycFinancialCondition, KycFinancialQuestion, KycFinancialQuestions, TfaSetup, diff --git a/packages/react/src/definitions/kyc.ts b/packages/react/src/definitions/kyc.ts index 0e7e23d5..a274bca1 100644 --- a/packages/react/src/definitions/kyc.ts +++ b/packages/react/src/definitions/kyc.ts @@ -38,6 +38,7 @@ export type { KycStepBase, KycStep, KycStepSession, + KycStepSubmit, KycContactData, KycAddress, KycPersonalData, @@ -59,6 +60,7 @@ export type { KycFinancialResponse, KycFinancialResponses, KycFinancialOption, + KycFinancialCondition, KycFinancialQuestion, KycFinancialQuestions, TfaSetup, diff --git a/packages/react/src/hooks/kyc.hook.ts b/packages/react/src/hooks/kyc.hook.ts index 0cf829eb..7173985f 100644 --- a/packages/react/src/hooks/kyc.hook.ts +++ b/packages/react/src/hooks/kyc.hook.ts @@ -21,6 +21,7 @@ import { TfaLevel, KycFile, KycStepBase, + KycStepSubmit, KycBeneficialData, KycOperationalData, PaymentData, @@ -52,7 +53,7 @@ export interface KycInterface { // updates setContactData: (code: string, url: string, data: KycContactData) => Promise; - setPersonalData: (code: string, url: string, data: KycPersonalData) => Promise; + setPersonalData: (code: string, url: string, data: KycPersonalData) => Promise; setManualIdentData: (code: string, url: string, data: KycManualIdentData) => Promise; setLegalEntityData: (code: string, url: string, data: KycLegalEntityData) => Promise; setSoleProprietorshipData: (code: string, url: string, data: KycFileData) => Promise; @@ -63,7 +64,7 @@ export interface KycInterface { setBeneficialData: (code: string, url: string, data: KycBeneficialData) => Promise; setOperationalData: (code: string, url: string, data: KycOperationalData) => Promise; getFinancialData: (code: string, url: string, lang?: string) => Promise; - setFinancialData: (code: string, url: string, data: KycFinancialResponses) => Promise; + setFinancialData: (code: string, url: string, data: KycFinancialResponses) => Promise; setPaymentData: (code: string, url: string, data: PaymentData) => Promise; setRecallData: (code: string, url: string, data: RecallData) => Promise; setAddressChangeData: (code: string, url: string, data: KycChangeAddressData) => Promise; @@ -175,7 +176,7 @@ export function useKyc(): KycInterface { ); const setPersonalData = useCallback( - async (code: string, url: string, data: KycPersonalData): Promise => { + async (code: string, url: string, data: KycPersonalData): Promise => { return call({ url, code, method: 'PUT', data }); }, [call], @@ -299,7 +300,7 @@ export function useKyc(): KycInterface { ); const setFinancialData = useCallback( - async (code: string, url: string, data: KycFinancialResponses): Promise => { + async (code: string, url: string, data: KycFinancialResponses): Promise => { return call({ url, code, method: 'PUT', data }); }, [call], diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index e749ddb3..f4f605b1 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -115,6 +115,7 @@ export { KycStepReason, KycStep, KycStepSession, + KycStepSubmit, KycContactData, KycAddress, KycPersonalData, @@ -122,6 +123,7 @@ export { KycFinancialResponse, KycFinancialResponses, KycFinancialOption, + KycFinancialCondition, KycFinancialQuestion, KycFinancialQuestions, KycManualIdentData, From 21a150e9a2d266d9235cac0113707590c314f11c Mon Sep 17 00:00:00 2001 From: joshuakrueger-dfx Date: Tue, 28 Jul 2026 12:20:43 +0200 Subject: [PATCH 2/4] test(kyc): cover submission completeness fields on the submit calls --- packages/core/src/__tests__/kyc-api.test.ts | 149 ++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 packages/core/src/__tests__/kyc-api.test.ts diff --git a/packages/core/src/__tests__/kyc-api.test.ts b/packages/core/src/__tests__/kyc-api.test.ts new file mode 100644 index 00000000..483ff733 --- /dev/null +++ b/packages/core/src/__tests__/kyc-api.test.ts @@ -0,0 +1,149 @@ +import { KycApi } from '../client/KycApi'; +import { DfxHttpClient } from '../client/DfxHttpClient'; +import { + AccountType, + KycFinancialResponses, + KycPersonalData, + KycStepName, + KycStepStatus, + KycStepSubmit, +} from '../definitions/kyc'; + +function createMockHttpClient(response?: unknown) { + const requestAbsoluteMock = jest.fn().mockResolvedValue(response); + + return { + request: jest.fn(), + requestAbsolute: requestAbsoluteMock, + getBaseUrl: jest.fn().mockReturnValue('https://api.dfx.swiss'), + getApiUrl: jest.fn().mockReturnValue('https://api.dfx.swiss/v1'), + setToken: jest.fn(), + getToken: jest.fn(), + } as unknown as DfxHttpClient & { requestAbsolute: jest.Mock }; +} + +/** Compile-time guard: fails tsc/ts-jest if the value is not a full KycStepSubmit. */ +function requireKycStepSubmit(value: KycStepSubmit): KycStepSubmit { + return value; +} + +const personalData: KycPersonalData = { + accountType: AccountType.PERSONAL, + firstName: 'Ada', + lastName: 'Lovelace', + phone: '+491701234567', + address: { + street: 'Main', + city: 'Berlin', + zip: '10115', + country: { + id: 1, + symbol: 'DE', + name: 'Germany', + locationAllowed: true, + kycAllowed: true, + nationalityAllowed: true, + bankAllowed: true, + cardAllowed: true, + cryptoAllowed: true, + kycOrganizationAllowed: true, + }, + }, +}; + +const financialData: KycFinancialResponses = { + responses: [{ key: 'income', value: '50000' }], +}; + +const submitUrl = 'https://api.dfx.swiss/v2/kyc/data/personal/42'; +const kycCode = 'kyc-code-1'; + +describe('KycApi', () => { + describe('setPersonalData', () => { + it('passes complete and missingFields through from the HTTP response unchanged', async () => { + const response: KycStepSubmit = { + name: KycStepName.PERSONAL_DATA, + status: KycStepStatus.IN_PROGRESS, + sequenceNumber: 1, + complete: false, + missingFields: ['address.city', 'phone'], + }; + const mockHttp = createMockHttpClient(response); + const api = new KycApi(mockHttp); + + const result = await api.setPersonalData(kycCode, submitUrl, personalData); + + // Runtime: fields must survive the client unchanged (no mapper that drops them). + expect(result.complete).toBe(false); + expect(result.missingFields).toEqual(['address.city', 'phone']); + expect(result).toEqual(response); + + // Type-level: return value must be a KycStepSubmit (breaks if return type is KycStepBase). + const typed = requireKycStepSubmit(result); + expect(typed.complete).toBe(false); + expect(typed.missingFields).toEqual(['address.city', 'phone']); + + expect(mockHttp.requestAbsolute).toHaveBeenCalledTimes(1); + expect(mockHttp.requestAbsolute).toHaveBeenCalledWith({ + url: submitUrl, + method: 'PUT', + data: personalData, + token: false, + headers: { 'x-kyc-code': kycCode }, + }); + }); + + it('passes complete=true with empty missingFields through unchanged', async () => { + const response: KycStepSubmit = { + name: KycStepName.PERSONAL_DATA, + status: KycStepStatus.IN_REVIEW, + sequenceNumber: 1, + complete: true, + missingFields: [], + }; + const mockHttp = createMockHttpClient(response); + const api = new KycApi(mockHttp); + + const result = await api.setPersonalData(kycCode, submitUrl, personalData); + requireKycStepSubmit(result); + + expect(result.complete).toBe(true); + expect(result.missingFields).toEqual([]); + }); + }); + + describe('setFinancialData', () => { + const financialUrl = 'https://api.dfx.swiss/v2/kyc/data/financial/7'; + + it('passes complete and missingFields through from the HTTP response unchanged', async () => { + const response: KycStepSubmit = { + name: KycStepName.FINANCIAL_DATA, + status: KycStepStatus.IN_PROGRESS, + sequenceNumber: 2, + complete: false, + missingFields: ['income', 'assets'], + }; + const mockHttp = createMockHttpClient(response); + const api = new KycApi(mockHttp); + + const result = await api.setFinancialData(kycCode, financialUrl, financialData); + + expect(result.complete).toBe(false); + expect(result.missingFields).toEqual(['income', 'assets']); + expect(result).toEqual(response); + + const typed = requireKycStepSubmit(result); + expect(typed.complete).toBe(false); + expect(typed.missingFields).toEqual(['income', 'assets']); + + expect(mockHttp.requestAbsolute).toHaveBeenCalledTimes(1); + expect(mockHttp.requestAbsolute).toHaveBeenCalledWith({ + url: financialUrl, + method: 'PUT', + data: financialData, + token: false, + headers: { 'x-kyc-code': kycCode }, + }); + }); + }); +}); From b6da3907d91d3dd6c1adcf360347c16188c28d60 Mon Sep 17 00:00:00 2001 From: joshuakrueger-dfx Date: Tue, 28 Jul 2026 12:36:28 +0200 Subject: [PATCH 3/4] test(kyc): cover financial question conditions in the api contract --- packages/core/src/__tests__/kyc-api.test.ts | 69 +++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/core/src/__tests__/kyc-api.test.ts b/packages/core/src/__tests__/kyc-api.test.ts index 483ff733..f1e96c27 100644 --- a/packages/core/src/__tests__/kyc-api.test.ts +++ b/packages/core/src/__tests__/kyc-api.test.ts @@ -2,11 +2,14 @@ import { KycApi } from '../client/KycApi'; import { DfxHttpClient } from '../client/DfxHttpClient'; import { AccountType, + KycFinancialCondition, + KycFinancialQuestions, KycFinancialResponses, KycPersonalData, KycStepName, KycStepStatus, KycStepSubmit, + QuestionType, } from '../definitions/kyc'; function createMockHttpClient(response?: unknown) { @@ -27,6 +30,16 @@ function requireKycStepSubmit(value: KycStepSubmit): KycStepSubmit { return value; } +/** Compile-time guard: fails if KycFinancialCondition loses question/response as string. */ +function requireKycFinancialCondition(value: KycFinancialCondition): KycFinancialCondition { + return value; +} + +/** Compile-time guard: fails if conditions is not KycFinancialCondition[]. */ +function requireKycFinancialConditions(value: KycFinancialCondition[]): KycFinancialCondition[] { + return value; +} + const personalData: KycPersonalData = { accountType: AccountType.PERSONAL, firstName: 'Ada', @@ -146,4 +159,60 @@ describe('KycApi', () => { }); }); }); + + describe('getFinancialData', () => { + const financialUrl = 'https://api.dfx.swiss/v2/kyc/data/financial/7'; + + it('passes question conditions through from the HTTP response unchanged', async () => { + const conditions: KycFinancialCondition[] = [{ question: 'occupation', response: 'employed' }]; + const response: KycFinancialQuestions = { + responses: [], + questions: [ + { + key: 'occupation_description', + type: QuestionType.TEXT, + title: 'Describe your occupation', + description: 'Only if employed', + conditions, + }, + { + key: 'income', + type: QuestionType.SINGLE_CHOICE, + title: 'Income', + description: 'Annual income', + }, + ], + }; + const mockHttp = createMockHttpClient(response); + const api = new KycApi(mockHttp); + + const result = await api.getFinancialData(kycCode, financialUrl, 'en'); + + // Runtime: conditions must survive the client unchanged (no mapper that drops them). + expect(result.questions[0].conditions).toEqual([{ question: 'occupation', response: 'employed' }]); + expect(result.questions[0].conditions?.[0].question).toBe('occupation'); + expect(result.questions[0].conditions?.[0].response).toBe('employed'); + expect(result).toEqual(response); + + // Type-level: conditions is KycFinancialCondition[] with string question/response. + const rawConditions = result.questions[0].conditions; + if (!rawConditions) { + throw new Error('expected conditions on first question'); + } + const typedConditions = requireKycFinancialConditions(rawConditions); + const typedCondition = requireKycFinancialCondition(typedConditions[0]); + const question: string = typedCondition.question; + const responseValue: string = typedCondition.response; + expect(question).toBe('occupation'); + expect(responseValue).toBe('employed'); + + expect(mockHttp.requestAbsolute).toHaveBeenCalledTimes(1); + expect(mockHttp.requestAbsolute).toHaveBeenCalledWith({ + url: `${financialUrl}?lang=en`, + method: 'GET', + token: false, + headers: { 'x-kyc-code': kycCode }, + }); + }); + }); }); From 6a782a312120f17b071949056538b700e69bd6b6 Mon Sep 17 00:00:00 2001 From: joshuakrueger-dfx Date: Fri, 31 Jul 2026 13:29:27 +0200 Subject: [PATCH 4/4] fix(kyc): type submission completeness as optional and slim the test Consumers pin this library independently and can run against an API deployment that does not report submission completeness yet. With both fields required, a consumer reading `result.complete` gets undefined and treats a submission that in fact succeeded as incomplete - the inverse of the symptom this contract is meant to remove. Both fields are now optional with a documented absence case, following ApiException.switchToCode. The test restated what the type system already enforces: three guard helpers plus the same fact asserted up to six times per case. Reverting the return type flags the plain field access before it reaches any of the helpers, so they detected nothing. What remains follows bank-api.test.ts - the exact call shape per endpoint, the response passing through unchanged, and one case for a response without the two fields, which only compiles while they are optional. --- packages/core/src/__tests__/kyc-api.test.ts | 82 +++++++-------------- packages/core/src/definitions/kyc.ts | 17 +++-- 2 files changed, 37 insertions(+), 62 deletions(-) diff --git a/packages/core/src/__tests__/kyc-api.test.ts b/packages/core/src/__tests__/kyc-api.test.ts index f1e96c27..07e46176 100644 --- a/packages/core/src/__tests__/kyc-api.test.ts +++ b/packages/core/src/__tests__/kyc-api.test.ts @@ -2,7 +2,6 @@ import { KycApi } from '../client/KycApi'; import { DfxHttpClient } from '../client/DfxHttpClient'; import { AccountType, - KycFinancialCondition, KycFinancialQuestions, KycFinancialResponses, KycPersonalData, @@ -25,21 +24,6 @@ function createMockHttpClient(response?: unknown) { } as unknown as DfxHttpClient & { requestAbsolute: jest.Mock }; } -/** Compile-time guard: fails tsc/ts-jest if the value is not a full KycStepSubmit. */ -function requireKycStepSubmit(value: KycStepSubmit): KycStepSubmit { - return value; -} - -/** Compile-time guard: fails if KycFinancialCondition loses question/response as string. */ -function requireKycFinancialCondition(value: KycFinancialCondition): KycFinancialCondition { - return value; -} - -/** Compile-time guard: fails if conditions is not KycFinancialCondition[]. */ -function requireKycFinancialConditions(value: KycFinancialCondition[]): KycFinancialCondition[] { - return value; -} - const personalData: KycPersonalData = { accountType: AccountType.PERSONAL, firstName: 'Ada', @@ -73,7 +57,7 @@ const kycCode = 'kyc-code-1'; describe('KycApi', () => { describe('setPersonalData', () => { - it('passes complete and missingFields through from the HTTP response unchanged', async () => { + it('returns complete=false and missingFields from the HTTP response', async () => { const response: KycStepSubmit = { name: KycStepName.PERSONAL_DATA, status: KycStepStatus.IN_PROGRESS, @@ -86,16 +70,8 @@ describe('KycApi', () => { const result = await api.setPersonalData(kycCode, submitUrl, personalData); - // Runtime: fields must survive the client unchanged (no mapper that drops them). - expect(result.complete).toBe(false); - expect(result.missingFields).toEqual(['address.city', 'phone']); expect(result).toEqual(response); - - // Type-level: return value must be a KycStepSubmit (breaks if return type is KycStepBase). - const typed = requireKycStepSubmit(result); - expect(typed.complete).toBe(false); - expect(typed.missingFields).toEqual(['address.city', 'phone']); - + expect(result.complete).toBe(false); expect(mockHttp.requestAbsolute).toHaveBeenCalledTimes(1); expect(mockHttp.requestAbsolute).toHaveBeenCalledWith({ url: submitUrl, @@ -106,7 +82,7 @@ describe('KycApi', () => { }); }); - it('passes complete=true with empty missingFields through unchanged', async () => { + it('returns complete=true with empty missingFields from the HTTP response', async () => { const response: KycStepSubmit = { name: KycStepName.PERSONAL_DATA, status: KycStepStatus.IN_REVIEW, @@ -118,17 +94,32 @@ describe('KycApi', () => { const api = new KycApi(mockHttp); const result = await api.setPersonalData(kycCode, submitUrl, personalData); - requireKycStepSubmit(result); + expect(result).toEqual(response); expect(result.complete).toBe(true); - expect(result.missingFields).toEqual([]); + }); + + it('returns undefined fields when the API does not report completeness', async () => { + const response: KycStepSubmit = { + name: KycStepName.PERSONAL_DATA, + status: KycStepStatus.IN_PROGRESS, + sequenceNumber: 1, + }; + const mockHttp = createMockHttpClient(response); + const api = new KycApi(mockHttp); + + const result = await api.setPersonalData(kycCode, submitUrl, personalData); + + expect(result).toEqual(response); + expect(result.complete).toBeUndefined(); + expect(result.missingFields).toBeUndefined(); }); }); describe('setFinancialData', () => { const financialUrl = 'https://api.dfx.swiss/v2/kyc/data/financial/7'; - it('passes complete and missingFields through from the HTTP response unchanged', async () => { + it('returns complete and missingFields from the HTTP response', async () => { const response: KycStepSubmit = { name: KycStepName.FINANCIAL_DATA, status: KycStepStatus.IN_PROGRESS, @@ -141,14 +132,8 @@ describe('KycApi', () => { const result = await api.setFinancialData(kycCode, financialUrl, financialData); - expect(result.complete).toBe(false); - expect(result.missingFields).toEqual(['income', 'assets']); expect(result).toEqual(response); - - const typed = requireKycStepSubmit(result); - expect(typed.complete).toBe(false); - expect(typed.missingFields).toEqual(['income', 'assets']); - + expect(result.missingFields).toEqual(['income', 'assets']); expect(mockHttp.requestAbsolute).toHaveBeenCalledTimes(1); expect(mockHttp.requestAbsolute).toHaveBeenCalledWith({ url: financialUrl, @@ -163,8 +148,7 @@ describe('KycApi', () => { describe('getFinancialData', () => { const financialUrl = 'https://api.dfx.swiss/v2/kyc/data/financial/7'; - it('passes question conditions through from the HTTP response unchanged', async () => { - const conditions: KycFinancialCondition[] = [{ question: 'occupation', response: 'employed' }]; + it('returns question conditions from the HTTP response', async () => { const response: KycFinancialQuestions = { responses: [], questions: [ @@ -173,7 +157,7 @@ describe('KycApi', () => { type: QuestionType.TEXT, title: 'Describe your occupation', description: 'Only if employed', - conditions, + conditions: [{ question: 'occupation', response: 'employed' }], }, { key: 'income', @@ -188,24 +172,8 @@ describe('KycApi', () => { const result = await api.getFinancialData(kycCode, financialUrl, 'en'); - // Runtime: conditions must survive the client unchanged (no mapper that drops them). - expect(result.questions[0].conditions).toEqual([{ question: 'occupation', response: 'employed' }]); - expect(result.questions[0].conditions?.[0].question).toBe('occupation'); - expect(result.questions[0].conditions?.[0].response).toBe('employed'); expect(result).toEqual(response); - - // Type-level: conditions is KycFinancialCondition[] with string question/response. - const rawConditions = result.questions[0].conditions; - if (!rawConditions) { - throw new Error('expected conditions on first question'); - } - const typedConditions = requireKycFinancialConditions(rawConditions); - const typedCondition = requireKycFinancialCondition(typedConditions[0]); - const question: string = typedCondition.question; - const responseValue: string = typedCondition.response; - expect(question).toBe('occupation'); - expect(responseValue).toBe('employed'); - + expect(result.questions[0].conditions).toEqual([{ question: 'occupation', response: 'employed' }]); expect(mockHttp.requestAbsolute).toHaveBeenCalledTimes(1); expect(mockHttp.requestAbsolute).toHaveBeenCalledWith({ url: `${financialUrl}?lang=en`, diff --git a/packages/core/src/definitions/kyc.ts b/packages/core/src/definitions/kyc.ts index 0bcc713b..c5ac3ef4 100644 --- a/packages/core/src/definitions/kyc.ts +++ b/packages/core/src/definitions/kyc.ts @@ -214,13 +214,20 @@ export interface KycStepSession extends KycStepBase { /** Response of KYC data submit endpoints (personal / financial). */ export interface KycStepSubmit extends KycStepBase { - /** Whether the submission fulfilled all required fields and the step advanced. When false, a draft was saved but the step did not progress. */ - complete: boolean; /** - * Missing required field paths (personal: e.g. `firstName`, `address.city`; financial: unanswered applicable question keys). - * Empty when `complete` is true. May also be empty while `complete` is false if the blocking field belongs to another step. + * Whether the submission fulfilled all required fields and the step advanced. + * When false, a draft was saved but the step did not progress. + * Absent on API versions that do not report submission completeness. */ - missingFields: string[]; + complete?: boolean; + /** + * Missing required field paths (personal: e.g. `firstName`, `address.city`; + * financial: unanswered applicable question keys). Empty when `complete` is + * true. May also be empty while `complete` is false if the blocking field + * belongs to another step. Absent on API versions that do not report + * submission completeness. + */ + missingFields?: string[]; } // personal data