mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 09:58:12 +00:00
160 lines
5.8 KiB
TypeScript
160 lines
5.8 KiB
TypeScript
import { client } from "@/utils/api";
|
|
import { unwrap } from "@/utils/endpoint";
|
|
import type { ApiResponse } from "@/types/apiResponse";
|
|
|
|
/**
|
|
* Which of the company's two people a verification is for.
|
|
*
|
|
* The **owner** is whoever the eTrade licence names as the business's manager
|
|
* — not necessarily the legal owner, but the person the record has to match.
|
|
* The **PoA** is who the company delegates to act for it.
|
|
*
|
|
* Exactly one of them is verified, chosen by the company's own answer to "does
|
|
* anyone hold power of attorney for you?" — see `poaDeclared`.
|
|
*/
|
|
export type IdentitySubject = "owner" | "poa";
|
|
|
|
/** Whether the company named a representative. Null until it answers. */
|
|
export type PoaDeclaration = "yes" | "no";
|
|
|
|
/** One person's identity state, as the API reports it. */
|
|
export interface IdentityVerificationState {
|
|
verified: boolean;
|
|
name: string | null;
|
|
phone: string | null;
|
|
email: string | null;
|
|
address: string | null;
|
|
verifiedAt: string | null;
|
|
/**
|
|
* Typed passport number — the ALTERNATIVE to Fayda for a foreign company,
|
|
* never written by a verification. Only asked of whichever person carries
|
|
* the company's identity, and only when `passportAccepted`.
|
|
*/
|
|
passportNumber: string | null;
|
|
}
|
|
|
|
export interface CompanyIdentityState {
|
|
/**
|
|
* True for a foreign company: a typed passport number proves the identity
|
|
* just as a Fayda verification does. Fayda is an Ethiopian national ID, so an
|
|
* Ethiopian company has no alternative to it.
|
|
*/
|
|
passportAccepted: boolean;
|
|
/**
|
|
* The company's answer to the power-of-attorney question. Null until it
|
|
* answers — which is itself outstanding, since the answer decides who
|
|
* verifies. Always "yes" for a freight forwarder, which cannot operate
|
|
* without a representative and is never asked.
|
|
*/
|
|
poaDeclared: PoaDeclaration | null;
|
|
/** Whose verification the company is gated on. Null while undeclared. */
|
|
subject: IdentitySubject | null;
|
|
owner: IdentityVerificationState;
|
|
poa: IdentityVerificationState;
|
|
/** True once `subject` is proven — Fayda-verified, or passport where accepted. */
|
|
identityProven: boolean;
|
|
/** The manager named on the eTrade licence, captured at lookup. */
|
|
etradeManagerName: string | null;
|
|
/**
|
|
* That manager's phone (E.164), from the same lookup. Together with the name
|
|
* this is what survives a refresh: the live lookup result does not, so
|
|
* without these two a resumed wizard cannot tell an eTrade-sourced owner from
|
|
* a typed one, and offers the licence's own data back as editable inputs.
|
|
*/
|
|
etradeManagerPhone: string | null;
|
|
/**
|
|
* Does the owner the company put forward match the eTrade licence? This is
|
|
* the backoffice's check. Null when there is nothing to compare. Advisory:
|
|
* eTrade's and Fayda's transliterations rarely agree exactly.
|
|
*/
|
|
ownerMatchesEtrade: boolean | null;
|
|
complete: boolean;
|
|
}
|
|
|
|
/**
|
|
* What the panel was doing when it handed the tab over to eSignet. The
|
|
* verification is a full-page redirect, so the page that started it is gone by
|
|
* the time /fayda/callback runs — this is how that page knows whose identity
|
|
* the code+state belongs to and where to put the user back.
|
|
*
|
|
* sessionStorage, not localStorage: it is scoped to this tab, so two tabs
|
|
* verifying different people can't overwrite each other, and it dies with the
|
|
* tab rather than outliving an abandoned verification.
|
|
*/
|
|
const PENDING_KEY = "fayda-pending-verification";
|
|
|
|
export interface PendingVerification {
|
|
subject: IdentitySubject;
|
|
/** Path to return to once the verification completes. */
|
|
returnTo: string;
|
|
}
|
|
|
|
export function stashPendingVerification(pending: PendingVerification): void {
|
|
sessionStorage.setItem(PENDING_KEY, JSON.stringify(pending));
|
|
}
|
|
|
|
/** Read and clear — the code+state are single-use, so a retry needs a fresh start. */
|
|
export function takePendingVerification(): PendingVerification | null {
|
|
const raw = sessionStorage.getItem(PENDING_KEY);
|
|
sessionStorage.removeItem(PENDING_KEY);
|
|
if (!raw) return null;
|
|
try {
|
|
const parsed = JSON.parse(raw) as PendingVerification;
|
|
return parsed.subject ? parsed : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export const verifaydaService = {
|
|
/**
|
|
* Returns the eSignet authorize URL to navigate the tab to. `PORTAL` selects
|
|
* the portal's own registered redirect_uri — the backoffice and mobile
|
|
* clients have their own.
|
|
*/
|
|
start: async (): Promise<string> => {
|
|
const response = await client.post<
|
|
ApiResponse<{ authorizationUrl: string }>
|
|
>("/api/fayda/verification/start", {
|
|
purpose: "VERIFY",
|
|
platform: "PORTAL",
|
|
});
|
|
return unwrap(response.data).authorizationUrl;
|
|
},
|
|
|
|
/**
|
|
* Exchange the callback code+state for a verified identity and bind it to one
|
|
* of the company's people. The API writes that person's name, phone, email
|
|
* and address from the Fayda payload — none of it is typed here.
|
|
*/
|
|
completeIdentity: async (
|
|
subject: IdentitySubject,
|
|
code: string,
|
|
state: string,
|
|
): Promise<CompanyIdentityState> => {
|
|
const response = await client.post<ApiResponse<CompanyIdentityState>>(
|
|
"/api/companies/identity/fayda/complete",
|
|
{ subject, code, state },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
/**
|
|
* Answer whether anyone holds power of attorney for this company — the
|
|
* question that decides whose identity is verified.
|
|
*
|
|
* Answering "no" tears the representative down server-side: their details,
|
|
* their verification, their passport number and the DARS delegation paper.
|
|
* Refused for a freight forwarder, which cannot operate without one.
|
|
*/
|
|
setPoaDeclared: async (
|
|
declared: PoaDeclaration,
|
|
): Promise<CompanyIdentityState> => {
|
|
const response = await client.patch<ApiResponse<CompanyIdentityState>>(
|
|
"/api/companies/identity/poa-declared",
|
|
{ declared },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
};
|