Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions packages/core/src/__tests__/kyc-api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { KycApi } from '../client/KycApi';
import { DfxHttpClient } from '../client/DfxHttpClient';
import {
AccountType,
KycFinancialQuestions,
KycFinancialResponses,
KycPersonalData,
KycStepName,
KycStepStatus,
KycStepSubmit,
QuestionType,
} 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 };
}

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('returns complete=false and missingFields from the HTTP response', 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);

expect(result).toEqual(response);
expect(result.complete).toBe(false);
expect(mockHttp.requestAbsolute).toHaveBeenCalledTimes(1);
expect(mockHttp.requestAbsolute).toHaveBeenCalledWith({
url: submitUrl,
method: 'PUT',
data: personalData,
token: false,
headers: { 'x-kyc-code': kycCode },
});
});

it('returns complete=true with empty missingFields from the HTTP response', 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);

expect(result).toEqual(response);
expect(result.complete).toBe(true);
});

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('returns complete and missingFields from the HTTP response', 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).toEqual(response);
expect(result.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 },
});
});
});

describe('getFinancialData', () => {
const financialUrl = 'https://api.dfx.swiss/v2/kyc/data/financial/7';

it('returns question conditions from the HTTP response', async () => {
const response: KycFinancialQuestions = {
responses: [],
questions: [
{
key: 'occupation_description',
type: QuestionType.TEXT,
title: 'Describe your occupation',
description: 'Only if employed',
conditions: [{ question: 'occupation', response: 'employed' }],
},
{
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');

expect(result).toEqual(response);
expect(result.questions[0].conditions).toEqual([{ question: 'occupation', response: 'employed' }]);
expect(mockHttp.requestAbsolute).toHaveBeenCalledTimes(1);
expect(mockHttp.requestAbsolute).toHaveBeenCalledWith({
url: `${financialUrl}?lang=en`,
method: 'GET',
token: false,
headers: { 'x-kyc-code': kycCode },
});
});
});
});
9 changes: 5 additions & 4 deletions packages/core/src/client/KycApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
KycInfo,
KycStepSession,
KycStepBase,
KycStepSubmit,
KycStepName,
KycStepType,
KycContactData,
Expand Down Expand Up @@ -75,8 +76,8 @@ export class KycApi {
return this.kycRequest<KycStepBase>(code, { url, method: 'PUT', data });
}

async setPersonalData(code: string, url: string, data: KycPersonalData): Promise<KycStepBase> {
return this.kycRequest<KycStepBase>(code, { url, method: 'PUT', data });
async setPersonalData(code: string, url: string, data: KycPersonalData): Promise<KycStepSubmit> {
return this.kycRequest<KycStepSubmit>(code, { url, method: 'PUT', data });
}

async setManualIdentData(code: string, url: string, data: KycManualIdentData): Promise<KycStepBase> {
Expand Down Expand Up @@ -120,8 +121,8 @@ export class KycApi {
return this.kycRequest<KycFinancialQuestions>(code, { url: `${url}${query}`, method: 'GET' });
}

async setFinancialData(code: string, url: string, data: KycFinancialResponses): Promise<KycStepBase> {
return this.kycRequest<KycStepBase>(code, { url, method: 'PUT', data });
async setFinancialData(code: string, url: string, data: KycFinancialResponses): Promise<KycStepSubmit> {
return this.kycRequest<KycStepSubmit>(code, { url, method: 'PUT', data });
}

async setPaymentData(code: string, url: string, data: PaymentData): Promise<KycStepBase> {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export type {
KycStepBase,
KycStep,
KycStepSession,
KycStepSubmit,
KycContactData,
KycAddress,
KycPersonalData,
Expand All @@ -94,6 +95,7 @@ export type {
KycFinancialResponse,
KycFinancialResponses,
KycFinancialOption,
KycFinancialCondition,
KycFinancialQuestion,
KycFinancialQuestions,
TfaSetup,
Expand Down
29 changes: 25 additions & 4 deletions packages/core/src/definitions/kyc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,24 @@ 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.
* Absent on API versions that do not report submission completeness.
*/
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
export interface KycContactData {
mail: string;
Expand Down Expand Up @@ -441,16 +459,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 {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export type {
KycStepBase,
KycStep,
KycStepSession,
KycStepSubmit,
KycContactData,
KycAddress,
KycPersonalData,
Expand All @@ -186,6 +187,7 @@ export type {
KycFinancialResponse,
KycFinancialResponses,
KycFinancialOption,
KycFinancialCondition,
KycFinancialQuestion,
KycFinancialQuestions,
TfaSetup,
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/definitions/kyc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type {
KycStepBase,
KycStep,
KycStepSession,
KycStepSubmit,
KycContactData,
KycAddress,
KycPersonalData,
Expand All @@ -59,6 +60,7 @@ export type {
KycFinancialResponse,
KycFinancialResponses,
KycFinancialOption,
KycFinancialCondition,
KycFinancialQuestion,
KycFinancialQuestions,
TfaSetup,
Expand Down
9 changes: 5 additions & 4 deletions packages/react/src/hooks/kyc.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
TfaLevel,
KycFile,
KycStepBase,
KycStepSubmit,
KycBeneficialData,
KycOperationalData,
PaymentData,
Expand Down Expand Up @@ -52,7 +53,7 @@ export interface KycInterface {

// updates
setContactData: (code: string, url: string, data: KycContactData) => Promise<KycStepBase>;
setPersonalData: (code: string, url: string, data: KycPersonalData) => Promise<KycStepBase>;
setPersonalData: (code: string, url: string, data: KycPersonalData) => Promise<KycStepSubmit>;
setManualIdentData: (code: string, url: string, data: KycManualIdentData) => Promise<KycStepBase>;
setLegalEntityData: (code: string, url: string, data: KycLegalEntityData) => Promise<KycStepBase>;
setSoleProprietorshipData: (code: string, url: string, data: KycFileData) => Promise<KycStepBase>;
Expand All @@ -63,7 +64,7 @@ export interface KycInterface {
setBeneficialData: (code: string, url: string, data: KycBeneficialData) => Promise<KycStepBase>;
setOperationalData: (code: string, url: string, data: KycOperationalData) => Promise<KycStepBase>;
getFinancialData: (code: string, url: string, lang?: string) => Promise<KycFinancialQuestions>;
setFinancialData: (code: string, url: string, data: KycFinancialResponses) => Promise<KycStepBase>;
setFinancialData: (code: string, url: string, data: KycFinancialResponses) => Promise<KycStepSubmit>;
setPaymentData: (code: string, url: string, data: PaymentData) => Promise<KycStepBase>;
setRecallData: (code: string, url: string, data: RecallData) => Promise<KycStepBase>;
setAddressChangeData: (code: string, url: string, data: KycChangeAddressData) => Promise<KycStepBase>;
Expand Down Expand Up @@ -175,7 +176,7 @@ export function useKyc(): KycInterface {
);

const setPersonalData = useCallback(
async (code: string, url: string, data: KycPersonalData): Promise<KycStepBase> => {
async (code: string, url: string, data: KycPersonalData): Promise<KycStepSubmit> => {
return call({ url, code, method: 'PUT', data });
},
[call],
Expand Down Expand Up @@ -299,7 +300,7 @@ export function useKyc(): KycInterface {
);

const setFinancialData = useCallback(
async (code: string, url: string, data: KycFinancialResponses): Promise<KycStepBase> => {
async (code: string, url: string, data: KycFinancialResponses): Promise<KycStepSubmit> => {
return call({ url, code, method: 'PUT', data });
},
[call],
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,15 @@ export {
KycStepReason,
KycStep,
KycStepSession,
KycStepSubmit,
KycContactData,
KycAddress,
KycPersonalData,
QuestionType,
KycFinancialResponse,
KycFinancialResponses,
KycFinancialOption,
KycFinancialCondition,
KycFinancialQuestion,
KycFinancialQuestions,
KycManualIdentData,
Expand Down
Loading