@creditchektechsupport/react-sdk 2.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 +22 -0
- package/README.md +490 -0
- package/dist/index.cjs +456 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +148 -0
- package/dist/index.d.ts +148 -0
- package/dist/index.js +425 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
Rewritten for the session-based CreditChek widget. See "Migrating from v1" in the README.
|
|
6
|
+
|
|
7
|
+
- New `useCreditChek` hook and `CreditChekButton` component.
|
|
8
|
+
- New `openCreditChekWidget` for use outside React components.
|
|
9
|
+
- The widget opens in a modal on your page, with an iframe, a loading state and enter/exit transitions. It becomes a full-screen sheet on phones. No other window or tab opens.
|
|
10
|
+
- Requires a `sessionId` created by your server with your secret key. `open` also accepts a function that creates the session on click.
|
|
11
|
+
- `onStep` reports each finished step; `onClose` reports the session ID and the steps once the widget closes; `onError` reports a failed session or invalid options.
|
|
12
|
+
- Only messages from the widget's frame or window, and its origin, are accepted. The widget is told this page's origin, so it addresses its messages to this page only.
|
|
13
|
+
- Supports the `identity` and `liveness` modules. Credit, income and Recova are coming later.
|
|
14
|
+
- Options: `publicKey`, `modules`, `environment`, `themeColor`, `prefill`, `widgetUrl`.
|
|
15
|
+
- `environment` (`"production"` or `"development"`) is passed to the widget as a URL parameter. Both environments use `https://securedwidget.creditchek.africa`; the development widget address is no longer used.
|
|
16
|
+
- `WIDGET_URL` exports the widget address.
|
|
17
|
+
- ESM and CommonJS builds with TypeScript types. Marked `"use client"` for Next.js. React is now a peer dependency (17+).
|
|
18
|
+
- Removed the default `creditchekSDK` export, and the `module`, `onComplete` and `postMessageParam` options.
|
|
19
|
+
|
|
20
|
+
## 1.0.1
|
|
21
|
+
|
|
22
|
+
- Initial public release.
|
package/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img title="CreditChek" height="200" src="https://docs.creditchek.africa/img/nav_logo.svg" width="50%"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# CreditChek React SDK
|
|
6
|
+
|
|
7
|
+
Add CreditChek identity verification to your React app. With a few lines of code your customers can:
|
|
8
|
+
|
|
9
|
+
- verify their identity with a **BVN** or **NIN**
|
|
10
|
+
- complete a **face liveness** check, matched against their BVN or NIN photo
|
|
11
|
+
|
|
12
|
+
The SDK opens the hosted CreditChek widget in a modal on your page, tells you as each step finishes, and tells you when the customer is done. It works with React 17+, Vite, Create React App and Next.js.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Contents
|
|
17
|
+
|
|
18
|
+
1. [How integration works](#how-integration-works)
|
|
19
|
+
2. [Before you start](#before-you-start)
|
|
20
|
+
3. [Installation](#installation)
|
|
21
|
+
4. [Step 1: Create a session on your server](#step-1-create-a-session-on-your-server)
|
|
22
|
+
5. [Step 2: Open the widget](#step-2-open-the-widget)
|
|
23
|
+
6. [Step 3: Know when the customer is done](#step-3-know-when-the-customer-is-done)
|
|
24
|
+
7. [Step 4: Confirm the result on your server](#step-4-confirm-the-result-on-your-server)
|
|
25
|
+
8. [Complete example](#complete-example)
|
|
26
|
+
9. [API reference](#api-reference)
|
|
27
|
+
10. [Modules](#modules)
|
|
28
|
+
11. [Testing](#testing)
|
|
29
|
+
12. [Errors and troubleshooting](#errors-and-troubleshooting)
|
|
30
|
+
13. [Security checklist](#security-checklist)
|
|
31
|
+
14. [Migrating from v1](#migrating-from-v1)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## How integration works
|
|
36
|
+
|
|
37
|
+
Every verification takes four steps. Two happen on your server and two in your React app:
|
|
38
|
+
|
|
39
|
+
| # | Where | What happens |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| 1 | **Your server** | Creates a **widget session** with your **secret key**, and returns its `sessionId` to your app |
|
|
42
|
+
| 2 | **Your React app** | Calls `open()` from this SDK. The widget opens in a modal with your **public key** and the `sessionId` |
|
|
43
|
+
| 3 | **Your React app** | The customer completes the steps. The SDK calls `onStep` as each one finishes, and `onClose` when the modal closes |
|
|
44
|
+
| 4 | **Your server** | Reads the session with your secret key to find out what the customer actually completed |
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Customer Your React app Your server CreditChek
|
|
48
|
+
│ click "Verify" │ │ │
|
|
49
|
+
│─────────────────▶│ POST /api/session │ │
|
|
50
|
+
│ │───────────────────────▶│ 1. create session │
|
|
51
|
+
│ │ │───────────────────────▶│
|
|
52
|
+
│ │ sessionId │◀───────────────────────│
|
|
53
|
+
│ │◀───────────────────────│ │
|
|
54
|
+
│ 2. modal opens (publicKey + sessionId) │ │
|
|
55
|
+
│◀─────────────────│ │ │
|
|
56
|
+
│ completes steps │ 3. onStep … onClose │ │
|
|
57
|
+
│─────────────────▶│ GET /api/session/:id │ │
|
|
58
|
+
│ │───────────────────────▶│ 4. read session │
|
|
59
|
+
│ │ │───────────────────────▶│
|
|
60
|
+
│ │ verified: true/false │◀───────────────────────│
|
|
61
|
+
│ │◀───────────────────────│ │
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Why a server?** Creating and reading sessions needs your secret key, and the secret key must never reach the browser. This SDK runs only in the browser and never touches your secret key.
|
|
65
|
+
|
|
66
|
+
**How the modal works.** The SDK adds a modal to your page with the widget inside an iframe, and removes it when the customer finishes, cancels, or hits an error screen. It slides up as a full-screen sheet on phones. The customer never leaves your page, and no other window or tab opens.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Before you start
|
|
71
|
+
|
|
72
|
+
**Keys.** Your keys are on the [CreditChek B2B dashboard](https://app.creditchek.africa/), in the **App** section. Each app has a live and a test pair:
|
|
73
|
+
|
|
74
|
+
| Key | Where it's used | Keep it secret? |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| **Secret key** | Your server only: creating and reading sessions | **Yes.** Never put it in React code, a `VITE_` / `NEXT_PUBLIC_` / `REACT_APP_` variable, or a URL |
|
|
77
|
+
| **Public key** | Your React app, passed to the SDK | No, it identifies your business |
|
|
78
|
+
|
|
79
|
+
Always use a secret key and a public key **from the same app and the same pair** (both live, or both test).
|
|
80
|
+
|
|
81
|
+
**API base URL** (your server): `https://api.creditchek.africa/v1`
|
|
82
|
+
|
|
83
|
+
**Your site's security headers.** If your site sends these headers, allow the widget in them:
|
|
84
|
+
|
|
85
|
+
| Header | What to allow |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `Content-Security-Policy` | `frame-src https://securedwidget.creditchek.africa` |
|
|
88
|
+
| `Permissions-Policy` | `camera=(self "https://securedwidget.creditchek.africa")`, so the liveness step can use the camera |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Installation
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm install @creditchektechsupport/react-sdk
|
|
96
|
+
# or
|
|
97
|
+
yarn add @creditchektechsupport/react-sdk
|
|
98
|
+
# or
|
|
99
|
+
pnpm add @creditchektechsupport/react-sdk
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`react` 17 or later is a peer dependency. The SDK has no other dependencies.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Step 1: Create a session on your server
|
|
107
|
+
|
|
108
|
+
Each verification needs its own widget session. The session records which services the customer must complete, and tracks their progress. Create it when the customer clicks to start: sessions are short-lived.
|
|
109
|
+
|
|
110
|
+
```http
|
|
111
|
+
POST https://api.creditchek.africa/v1/auth/widget-session/token
|
|
112
|
+
token: <your secret key>
|
|
113
|
+
Content-Type: application/json
|
|
114
|
+
|
|
115
|
+
{ "services": ["bvn", "liveness"] }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Field | Type | Description |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `services` | `("bvn" \| "nin" \| "liveness")[]` | Services for this verification. Defaults to `["bvn"]` |
|
|
121
|
+
| `sessionId` | `string` | Optional. Your own unique ID for the session |
|
|
122
|
+
|
|
123
|
+
The services decide which identity document the customer uses: `bvn` and `nin` lets them choose, `nin` only means NIN, and otherwise it's BVN.
|
|
124
|
+
|
|
125
|
+
**Example** (Node.js 18+ with Express; any server language works):
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
// server.js
|
|
129
|
+
import express from "express";
|
|
130
|
+
|
|
131
|
+
const API_BASE = "https://api.creditchek.africa/v1";
|
|
132
|
+
const SECRET_KEY = process.env.CREDITCHEK_SECRET_KEY; // server only
|
|
133
|
+
|
|
134
|
+
async function creditchek(path, init = {}) {
|
|
135
|
+
const res = await fetch(`${API_BASE}${path}`, {
|
|
136
|
+
...init,
|
|
137
|
+
headers: { "Content-Type": "application/json", token: SECRET_KEY, ...init.headers },
|
|
138
|
+
});
|
|
139
|
+
const body = await res.json();
|
|
140
|
+
if (!res.ok || !body.success) throw new Error(body.message ?? `CreditChek HTTP ${res.status}`);
|
|
141
|
+
return body.data;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const app = express();
|
|
145
|
+
|
|
146
|
+
app.post("/api/verification/session", async (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
const session = await creditchek("/auth/widget-session/token", {
|
|
149
|
+
method: "POST",
|
|
150
|
+
body: JSON.stringify({ services: ["bvn", "liveness"] }),
|
|
151
|
+
});
|
|
152
|
+
await saveSessionForCustomer(req, session.sessionId); // your storage
|
|
153
|
+
res.json({ sessionId: session.sessionId });
|
|
154
|
+
} catch {
|
|
155
|
+
res.status(502).json({ error: "Could not start verification" });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The response `data` looks like this:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"sessionId": "a78e8b61-469b-4ec6-8d1a-5f0ef30d927c",
|
|
165
|
+
"status": "active",
|
|
166
|
+
"services": { "bvn": { "status": "pending" }, "liveness": { "status": "pending" } },
|
|
167
|
+
"expiresAt": "2026-09-09T14:10:00.000Z"
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Step 2: Open the widget
|
|
174
|
+
|
|
175
|
+
Use the `useCreditChek` hook and call `open` from a click handler:
|
|
176
|
+
|
|
177
|
+
```jsx
|
|
178
|
+
import { useCreditChek } from "@creditchektechsupport/react-sdk";
|
|
179
|
+
|
|
180
|
+
async function createSession() {
|
|
181
|
+
const res = await fetch("/api/verification/session", { method: "POST" });
|
|
182
|
+
if (!res.ok) throw new Error("Could not start verification");
|
|
183
|
+
return (await res.json()).sessionId;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function VerifyButton() {
|
|
187
|
+
const { open, isOpen } = useCreditChek({
|
|
188
|
+
publicKey: "YOUR_PUBLIC_KEY",
|
|
189
|
+
modules: ["identity", "liveness"],
|
|
190
|
+
onClose: ({ sessionId }) => {
|
|
191
|
+
// Step 3: the customer is done. Ask your server for the result.
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<button onClick={() => open(createSession)} disabled={isOpen}>
|
|
197
|
+
Verify my identity
|
|
198
|
+
</button>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Pass the session as a function** (`open(createSession)`). The modal opens straight away with a loading spinner, calls your function, then loads the widget. If you already have a session ID, `open("the-session-id")` works too.
|
|
204
|
+
|
|
205
|
+
Prefer a ready-made button? `CreditChekButton` does the same. Any other props go to the `<button>`:
|
|
206
|
+
|
|
207
|
+
```jsx
|
|
208
|
+
import { CreditChekButton } from "@creditchektechsupport/react-sdk";
|
|
209
|
+
|
|
210
|
+
<CreditChekButton
|
|
211
|
+
publicKey="YOUR_PUBLIC_KEY"
|
|
212
|
+
sessionId={createSession}
|
|
213
|
+
modules={["identity", "liveness"]}
|
|
214
|
+
onClose={({ sessionId }) => checkResult(sessionId)}
|
|
215
|
+
className="btn btn-primary"
|
|
216
|
+
>
|
|
217
|
+
Verify my identity
|
|
218
|
+
</CreditChekButton>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Step 3: Know when the customer is done
|
|
224
|
+
|
|
225
|
+
The SDK gives you two callbacks:
|
|
226
|
+
|
|
227
|
+
| Callback | When | Use it for |
|
|
228
|
+
|---|---|---|
|
|
229
|
+
| `onStep({ module, status })` | Each time a step finishes in the widget | Progress in your UI, analytics |
|
|
230
|
+
| `onClose({ sessionId, steps })` | Once, when the widget closes for any reason: the customer finished, hit an error screen, or closed it | Asking your server for the result |
|
|
231
|
+
|
|
232
|
+
> **The widget closing means "the customer is done", not "the customer passed".** Step events are hints for your UI. Always decide the outcome on your server (Step 4).
|
|
233
|
+
|
|
234
|
+
If something goes wrong before the widget loads, `onError` fires instead of `onClose`. See [Errors](#errors-and-troubleshooting).
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Step 4: Confirm the result on your server
|
|
239
|
+
|
|
240
|
+
The session is the source of truth:
|
|
241
|
+
|
|
242
|
+
```http
|
|
243
|
+
GET https://api.creditchek.africa/v1/auth/widget-session/<sessionId>
|
|
244
|
+
token: <your secret key>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
// server.js (continued)
|
|
249
|
+
app.get("/api/verification/session/:sessionId", async (req, res) => {
|
|
250
|
+
try {
|
|
251
|
+
if (!(await customerOwnsSession(req, req.params.sessionId))) return res.sendStatus(404);
|
|
252
|
+
const session = await creditchek(`/auth/widget-session/${encodeURIComponent(req.params.sessionId)}`);
|
|
253
|
+
const services = Object.values(session.services);
|
|
254
|
+
const verified = services.length > 0 && services.every((s) => s?.status === "completed");
|
|
255
|
+
res.json({ verified, services: session.services });
|
|
256
|
+
} catch {
|
|
257
|
+
res.status(502).json({ error: "Could not read verification" });
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| Field | Value | Meaning |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| `services.<name>.status` | `pending` | Not done yet, or the customer left before finishing |
|
|
265
|
+
| | `completed` | Verified |
|
|
266
|
+
| | `failed` | The last attempt failed. Liveness can be retried in the same session |
|
|
267
|
+
| `status` | `active` | Services are still outstanding |
|
|
268
|
+
| | `completed` | Every service on the session is completed |
|
|
269
|
+
|
|
270
|
+
For the name, date of birth and gender behind a completed BVN, call `GET /auth/widget-session/bvn-data/<sessionId>` with your secret key.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Complete example
|
|
275
|
+
|
|
276
|
+
```jsx
|
|
277
|
+
import { useState } from "react";
|
|
278
|
+
import { useCreditChek } from "@creditchektechsupport/react-sdk";
|
|
279
|
+
|
|
280
|
+
export default function Verification({ customer }) {
|
|
281
|
+
const [message, setMessage] = useState("");
|
|
282
|
+
|
|
283
|
+
const { open, isOpen, steps } = useCreditChek({
|
|
284
|
+
publicKey: import.meta.env.VITE_CREDITCHEK_PUBLIC_KEY,
|
|
285
|
+
modules: ["identity", "liveness"],
|
|
286
|
+
themeColor: "#0046E6",
|
|
287
|
+
prefill: { firstName: customer.firstName, lastName: customer.lastName },
|
|
288
|
+
onClose: async ({ sessionId }) => {
|
|
289
|
+
setMessage("Checking your result…");
|
|
290
|
+
const result = await fetch(`/api/verification/session/${sessionId}`).then((r) => r.json());
|
|
291
|
+
setMessage(result.verified ? "You're verified." : "Verification isn't complete yet.");
|
|
292
|
+
},
|
|
293
|
+
onError: () => setMessage("Something went wrong. Please try again."),
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const createSession = async () => {
|
|
297
|
+
const res = await fetch("/api/verification/session", { method: "POST" });
|
|
298
|
+
if (!res.ok) throw new Error("Could not start verification");
|
|
299
|
+
return (await res.json()).sessionId;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div>
|
|
304
|
+
<button onClick={() => open(createSession)} disabled={isOpen}>
|
|
305
|
+
Verify my identity
|
|
306
|
+
</button>
|
|
307
|
+
<ul>
|
|
308
|
+
{steps.map((s, i) => (
|
|
309
|
+
<li key={i}>{s.module}: {s.status}</li>
|
|
310
|
+
))}
|
|
311
|
+
</ul>
|
|
312
|
+
<p>{message}</p>
|
|
313
|
+
</div>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## API reference
|
|
321
|
+
|
|
322
|
+
### `useCreditChek(options)`
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
const { open, close, isOpen, steps, error } = useCreditChek(options);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Options**
|
|
329
|
+
|
|
330
|
+
| Option | Type | Default | Description |
|
|
331
|
+
|---|---|---|---|
|
|
332
|
+
| `publicKey` | `string` | **required** | Your public key. Must be from the same app and pair as the secret key that created the session |
|
|
333
|
+
| `modules` | `("identity" \| "liveness")[]` | `["identity"]` | Steps to run, in order. See [Modules](#modules) |
|
|
334
|
+
| `environment` | `"production" \| "development"` | from your key | `"development"` runs the widget in test mode and shows a Test mode notice. Left out, the widget takes live or test mode from your public key. See [Testing](#testing) |
|
|
335
|
+
| `themeColor` | `string` | | Brand colour as hex, with or without `#`. Also colours the modal's loading spinner |
|
|
336
|
+
| `prefill` | `WidgetPrefill` | | Details you already hold: `firstName`, `lastName`, `dob` (`YYYY-MM-DD`), `bvn`, `nin`, `email`. Prefilled identity fields are locked in the widget |
|
|
337
|
+
| `onStep` | `(event: WidgetStepEvent) => void` | | A step finished |
|
|
338
|
+
| `onClose` | `(result: WidgetCloseResult) => void` | | The widget closed |
|
|
339
|
+
| `onError` | `(error: CreditChekError) => void` | | The widget couldn't open |
|
|
340
|
+
|
|
341
|
+
Options are read when `open` is called, so they can change between renders.
|
|
342
|
+
|
|
343
|
+
**Returns**
|
|
344
|
+
|
|
345
|
+
| Field | Type | Description |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| `open` | `(sessionId: string \| () => string \| Promise<string>) => void` | Opens the widget. If it's already open, focuses it |
|
|
348
|
+
| `close` | `() => void` | Closes the widget. `onClose` fires as normal |
|
|
349
|
+
| `isOpen` | `boolean` | True from `open` until the widget closes or fails |
|
|
350
|
+
| `steps` | `WidgetStepEvent[]` | Step events since the last `open` |
|
|
351
|
+
| `error` | `CreditChekError \| null` | The error from the last `open` |
|
|
352
|
+
|
|
353
|
+
If the component unmounts while the widget is open, the modal is removed and callbacks stop firing.
|
|
354
|
+
|
|
355
|
+
### `<CreditChekButton />`
|
|
356
|
+
|
|
357
|
+
Takes every `useCreditChek` option, plus `sessionId` (a string or a function), plus any `<button>` props. `children` defaults to "Verify with CreditChek". An `onClick` prop runs before the widget opens; call `event.preventDefault()` in it to stop the widget opening.
|
|
358
|
+
|
|
359
|
+
### `openCreditChekWidget(options)`
|
|
360
|
+
|
|
361
|
+
The same behaviour without React, for use outside components. Takes every `useCreditChek` option plus `sessionId`, and returns a handle:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
const handle = openCreditChekWidget({ publicKey, sessionId: createSession, onClose });
|
|
365
|
+
handle.isOpen; // boolean
|
|
366
|
+
handle.focus(); // focus the widget
|
|
367
|
+
handle.close(); // close the widget; onClose fires
|
|
368
|
+
handle.detach(); // remove the modal without calling onClose
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Exactly one of `onClose` or `onError` fires for each call.
|
|
372
|
+
|
|
373
|
+
### `buildWidgetUrl(config, sessionId)`
|
|
374
|
+
|
|
375
|
+
Returns the full widget URL, if you want to host the widget yourself.
|
|
376
|
+
|
|
377
|
+
### Types
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
type WidgetModule = "identity" | "liveness";
|
|
381
|
+
|
|
382
|
+
interface WidgetStepEvent {
|
|
383
|
+
module: WidgetModule;
|
|
384
|
+
status: "successful" | "failed";
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
interface WidgetCloseResult {
|
|
388
|
+
sessionId: string;
|
|
389
|
+
steps: WidgetStepEvent[]; // oldest first
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
All types are exported from the package.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Modules
|
|
398
|
+
|
|
399
|
+
| Module | What the customer does | What they need |
|
|
400
|
+
|---|---|---|
|
|
401
|
+
| `identity` | Enters their name, date of birth and BVN or NIN. At least two name words and the exact date of birth must match the official record | Their 11-digit BVN or NIN |
|
|
402
|
+
| `liveness` | Centres their face in the camera, then follows prompts: look left, right, up and down, blink, smile. Their face is matched against the BVN or NIN photo | A camera, good lighting, no glasses or hat |
|
|
403
|
+
|
|
404
|
+
- **Put `identity` before `liveness`**, in the same session: `modules: ["identity", "liveness"]`. Liveness compares the customer's face with the photo from their BVN or NIN.
|
|
405
|
+
- **Completed steps are skipped.** If identity or liveness is already `completed` on the session, the widget skips it. If both are, the customer sees a success screen straight away.
|
|
406
|
+
|
|
407
|
+
More modules (credit reports, income and Recova mandates) are coming to the React SDK.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Testing
|
|
412
|
+
|
|
413
|
+
Use the **test** public and secret keys from the App section of the [dashboard](https://app.creditchek.africa/). They work with the same API URL and the same widget as your live keys.
|
|
414
|
+
|
|
415
|
+
Set `environment` to match the keys, so the widget runs in the right mode:
|
|
416
|
+
|
|
417
|
+
```jsx
|
|
418
|
+
useCreditChek({
|
|
419
|
+
publicKey: import.meta.env.VITE_CREDITCHEK_PUBLIC_KEY,
|
|
420
|
+
environment: import.meta.env.PROD ? "production" : "development",
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
`environment` is sent to the widget in its URL. It doesn't change the widget address: both environments use `https://securedwidget.creditchek.africa`.
|
|
425
|
+
|
|
426
|
+
**Next.js:** the package is marked `"use client"`, so you can import it into App Router client components directly. Keep the session routes in a Route Handler or API route, where the secret key stays on the server.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Errors and troubleshooting
|
|
431
|
+
|
|
432
|
+
**`CreditChekError` codes** (passed to `onError` and stored in `error`):
|
|
433
|
+
|
|
434
|
+
| Code | Cause | Fix |
|
|
435
|
+
|---|---|---|
|
|
436
|
+
| `session_failed` | Your `sessionId` function threw or returned nothing | Check your server route. The widget is closed for you |
|
|
437
|
+
| `invalid_config` | `publicKey` is missing or `modules` is empty | Fix the options |
|
|
438
|
+
|
|
439
|
+
**Error screens inside the widget.** If the widget can't start, the customer sees an error screen with a code and their session ID. Closing it closes the widget, so `onClose` fires.
|
|
440
|
+
|
|
441
|
+
| Code on screen | Cause | Fix |
|
|
442
|
+
|---|---|---|
|
|
443
|
+
| `missing` / `invalid` | No session, or it doesn't exist | Pass the `sessionId` from Step 1 unmodified |
|
|
444
|
+
| `expired` | The session expired | Create the session when the customer clicks, not in advance |
|
|
445
|
+
| `inactive` | The session was already completed | Create a new session for each verification |
|
|
446
|
+
| `mismatch` | Session and public key are from different apps | Use the secret and public keys of the **same** app |
|
|
447
|
+
| `validate-public-key` | The public key is invalid | Check the key, and that it's from the same live or test pair as the secret key |
|
|
448
|
+
|
|
449
|
+
**Common problems**
|
|
450
|
+
|
|
451
|
+
| Symptom | Likely cause |
|
|
452
|
+
|---|---|
|
|
453
|
+
| The modal stays blank | Your `Content-Security-Policy` blocks the widget. Add it to `frame-src` |
|
|
454
|
+
| The camera doesn't start | The customer denied camera access, or your `Permissions-Policy` blocks the camera for the widget |
|
|
455
|
+
| The camera doesn't start inside Instagram, WhatsApp or similar apps | Their in-app browsers often block the camera. Ask the customer to open your site in their browser |
|
|
456
|
+
| Liveness can't match the face | `liveness` ran without `identity` before it in the same session |
|
|
457
|
+
| Your server shows `pending` | The customer closed the widget before finishing |
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Security checklist
|
|
462
|
+
|
|
463
|
+
- [ ] The secret key lives only on your server.
|
|
464
|
+
- [ ] You create one session per verification, when the customer starts.
|
|
465
|
+
- [ ] Your server stores each `sessionId` against its customer, and only reports a session's result to that customer.
|
|
466
|
+
- [ ] You decide "verified" on your server from `GET /auth/widget-session/:sessionId`, never from `onStep` or `onClose` alone.
|
|
467
|
+
- [ ] Your pages are served over HTTPS in production.
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Migrating from v1
|
|
472
|
+
|
|
473
|
+
v2 follows the new session-based widget, which needs a `sessionId` created by your server.
|
|
474
|
+
|
|
475
|
+
| v1 | v2 |
|
|
476
|
+
|---|---|
|
|
477
|
+
| `import creditchekSDK from "creditchek-react-sdk"` | `import { useCreditChek } from "@creditchektechsupport/react-sdk"` |
|
|
478
|
+
| `creditchekSDK.open({ publicKey, module, onComplete, onClose })` | `const { open } = useCreditChek({ publicKey, modules, onStep, onClose })`, then `open(sessionId)` |
|
|
479
|
+
| `module: ["identity"]` | `modules: ["identity"]` or `["identity", "liveness"]` |
|
|
480
|
+
| `"income"`, `"credit"`, `"recova"` modules | Not available in v2 yet |
|
|
481
|
+
| `onComplete(result)` for each step | `onStep({ module, status })` |
|
|
482
|
+
| `onClose()` | `onClose({ sessionId, steps })` |
|
|
483
|
+
| Opens a new tab | Opens a modal on your page |
|
|
484
|
+
| No server needed | Your server creates and reads sessions ([Step 1](#step-1-create-a-session-on-your-server), [Step 4](#step-4-confirm-the-result-on-your-server)) |
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Support
|
|
489
|
+
|
|
490
|
+
For help with this library, open an issue on the [GitHub repo](https://github.com/CreditChekTechSupport/approval-web/issues) or email [support@creditchek.africa](mailto:support@creditchek.africa).
|