@finverse/sdk-typescript 0.0.373 → 0.0.374

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.
Files changed (4) hide show
  1. package/README.md +378 -212
  2. package/dist/api.d.ts +0 -261
  3. package/dist/api.js +0 -490
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,293 +1,459 @@
1
- # Finverse API - Typescript SDK
2
- This SDK enables a basic end-to-end backend integration with the Finverse API, including API authentication, institution linking, and data retrieval.
1
+ # Finverse API - TypeScript SDK
2
+
3
+ This SDK enables end-to-end backend integration with the Finverse API, including API authentication, institution linking, data retrieval, and payment operations.
3
4
 
4
5
  ## Installation
6
+
5
7
  ```
6
8
  npm install @finverse/sdk-typescript
7
9
  ```
8
10
 
9
- ## Getting started (Linking flow)
11
+ ## API Overview
12
+
13
+ The SDK is organized into two main API categories:
14
+
15
+ | API | Purpose |
16
+ |-----|---------|
17
+ | **Payment API** | Create payment links, mandates, payments, and manage payment accounts |
18
+ | **Data API** | Retrieve accounts, transactions, statements, balance history, and identity data |
19
+
20
+ Both APIs share authentication and linking flows via `PublicApi` and `LinkApi`.
21
+
22
+ ---
23
+
24
+ ## Payment API
25
+
26
+ The Payment API enables you to create payment links for checkout, initiate payments, manage mandates, and handle payment-related operations.
27
+
28
+ ### Key Classes & Methods
29
+
30
+ | Class | Key Methods |
31
+ |-------|-------------|
32
+ | `CustomerApi` | `createPayment`, `getPayment`, `createMandate`, `getMandate`, `authorizeMandate`, `createPaymentUser`, `createPaymentAccount`, `listPaymentAccounts` |
33
+ | `DefaultApi` | `createPaymentLink`, `getPaymentLink`, `cancelPaymentLink`, `createScheduledPayout`, `getPayoutById`, `createPaymentMethod` |
34
+
35
+ ### Payment Flow (End-to-End)
36
+
37
+ The payment link flow: **Create** → **Redirect** → **Callback** → **Poll** → **Result**.
38
+
39
+ 1. **Create** – Create payment link via API, get `url` and `payment_link_id`. Save both and `unique_reference_id`.
40
+ 2. **Redirect** – Redirect user to the payment link URL (use HTTP 303 so the redirect is followed with GET).
41
+ 3. **Callback** – Finverse redirects back with `payment_link_id` and `unique_reference_id` query params. Verify they match what you stored.
42
+ 4. **Poll** – Poll `GET /payment_links/{paymentLinkId}` every 2 seconds for up to 30 seconds until `session_status` is `"COMPLETE"`.
43
+ 5. **Result** – If `COMPLETE` → success; otherwise → error.
44
+
45
+ > **Security:** `FINVERSE_CLIENT_ID` and `FINVERSE_CLIENT_SECRET` must **never** be committed to git or sent to the frontend. Use them only on the server (e.g. environment variables, secrets manager).
46
+
47
+ #### 1. Authenticate: Obtain Customer Access Token
10
48
 
11
- ### 1. Authenticate with Finverse API: Obtain Customer Access Token
12
49
  ```typescript
13
- // Obtain these from https://dashboard.finverse.com
14
- const apiHost = "https://api.sandbox.finverse.net"
15
- const clientId = process.env.FINVERSE_CLIENTID
16
- const clientSecret = process.env.FINVERSE_SECRET
17
- const redirectUri = process.env.REDIRECT_URI
50
+ import { Configuration, PublicApi } from '@finverse/sdk-typescript';
51
+
52
+ const apiHost = process.env.FINVERSE_BASE_URL ?? "https://api.prod.finverse.net";
53
+ const clientId = process.env.FINVERSE_CLIENT_ID;
54
+ const clientSecret = process.env.FINVERSE_CLIENT_SECRET;
18
55
 
19
56
  const configuration = new Configuration({ basePath: apiHost });
20
- // Obtain customer access token
21
57
  const customerTokenResp = await new PublicApi(configuration).generateCustomerAccessToken({
22
- client_id: clientId,
23
- client_secret: clientSecret,
24
- grant_type: 'client_credentials',
58
+ client_id: clientId,
59
+ client_secret: clientSecret,
60
+ grant_type: 'client_credentials',
25
61
  });
26
62
 
27
- const customerAccessToken = customerTokenResp.access_token
63
+ const customerAccessToken = customerTokenResp.data.access_token;
64
+ // Cache the token; refresh before expiry (use expires_in minus 60s buffer)
28
65
  ```
29
66
 
30
- ### 2. Link new institution: Obtain Link Token and Link URL to launch Finverse Link UI
67
+ #### 2. Create Payment Link
68
+
69
+ **Payment mode** – For one-time payments. Requires `amount` (minor units, e.g. 10000 = 100.00 HKD), `currency`, `sender`, `payment_details`, and `link_customizations` with `redirect_uri`:
70
+
31
71
  ```typescript
32
- // generate a link token
33
-
34
- // reference back to your system userId, finverse does not use this
35
- const userId = "someUserId"
36
- // this will be sent in the redirectUri callback, can be used to identify the state
37
- const state = "someUniqueState"
38
- const configuration = new Configuration({
39
- basePath: apiHost,
40
- accessToken: customerToken.access_token
72
+ import { DefaultApi } from '@finverse/sdk-typescript';
73
+ import crypto from 'crypto';
74
+
75
+ const configuration = new Configuration({
76
+ basePath: apiHost,
77
+ accessToken: customerAccessToken,
41
78
  });
42
- const linkTokenResp = await new CustomerApi(configuration).generateLinkToken({
43
- client_id: clientId,
44
- user_id: userId,
45
- redirect_uri: redirectUri,
46
- state: state,
47
- response_mode: "form_post",
48
- response_type: "code",
49
- grant_type: "client_credentials",
79
+ const defaultApi = new DefaultApi(configuration);
80
+
81
+ const uniqueReferenceId = crypto.randomUUID();
82
+ const callbackUrl = process.env.CALLBACK_URL; // e.g. https://yoursite.com/callback
83
+
84
+ const createResp = await defaultApi.createPaymentLink({
85
+ mode: "PAYMENT",
86
+ amount: 10000, // 100.00 HKD
87
+ currency: "HKD",
88
+ unique_reference_id: uniqueReferenceId,
89
+ sender: {
90
+ external_user_id: "your-internal-user-id",
91
+ name: "Customer Name",
92
+ email: "customer@example.com",
93
+ },
94
+ payment_details: {
95
+ description: "Order #12345",
96
+ external_transaction_reference: "order-12345", // max 35 chars
97
+ },
98
+ link_customizations: {
99
+ ui_mode: "redirect",
100
+ redirect_uri: callbackUrl,
101
+ },
50
102
  });
51
103
 
52
- // The linkUrl can be used to initiate Finverse Link
53
- console.log("linkUrl: " + linkTokenResp.link_url)
104
+ const paymentLinkId = createResp.data.payment_link_id;
105
+ const paymentUrl = createResp.data.url;
106
+
107
+ // Store paymentLinkId and uniqueReferenceId for callback verification
54
108
  ```
55
109
 
56
- ### 3. Finalize linking: Exchange code for Login Identity Access Token
110
+ **Setup mode** For saving payment methods (e.g. Click to Pay). Do **not** pass `amount`:
111
+
57
112
  ```typescript
58
- // when Finverse Link UI is successful, obtain the code from Finverse Link
59
- // exchange it for a Login Identity Access Token
60
- const code = "obtainAfterLink"
61
- const configuration = new Configuration({
62
- basePath: apiHost,
63
- accessToken: customerToken.access_token
113
+ const createResp = await defaultApi.createPaymentLink({
114
+ mode: "SETUP",
115
+ currency: "HKD",
116
+ unique_reference_id: uniqueReferenceId,
117
+ sender: {
118
+ external_user_id: "your-internal-user-id",
119
+ name: "Customer Name",
120
+ email: "customer@example.com",
121
+ },
122
+ payment_details: {
123
+ description: "Save payment method",
124
+ external_transaction_reference: "setup-12345",
125
+ },
126
+ link_customizations: {
127
+ ui_mode: "redirect",
128
+ redirect_uri: callbackUrl,
129
+ },
130
+ payment_setup_options: {
131
+ future_payments: "CLICK_TO_PAY",
132
+ },
64
133
  });
65
- const loginIdentityTokenResp = await new LinkApi(configuration).token(
66
- "authorization_code",
67
- code,
68
- clientId,
69
- redirectURI,
70
- );
71
-
72
- // The loginIdentityToken can be used to retrieve data
73
- const loginIdentityToken = loginIdentityTokenResp.access_token
74
134
  ```
75
135
 
76
- ### 4. Retrieve data: Get data using Login Identity Access Token
77
- ```typescript
78
- // get LoginIdentity
79
- const configuration = new Configuration({
80
- basePath: apiHost,
81
- accessToken: loginIdentityToken.access_token
82
- });
83
- const loginIdentityResp = await new LoginIdentityApi(configuration).getLoginIdentity();
136
+ #### 3. Redirect User to Payment URL
84
137
 
138
+ Redirect the user to `paymentUrl`. **Use HTTP 303** so the browser follows with GET (Finverse expects GET):
85
139
 
86
- console.log("login identity: " + loginIdentityResp.login_identity)
87
-
88
- // get other products (Accounts, Account Numbers, Transactions)
89
- ```
90
140
 
141
+ #### 4. Callback Handler: Poll for Completion
142
+
143
+ When Finverse redirects back, read `payment_link_id` and `unique_reference_id` from query params. Verify they match your stored values, then poll until `session_status` is `COMPLETE`:
91
144
 
92
- ### 5. Poll loginIdentityStatus until ready
93
145
  ```typescript
94
- enum FinalStatus {
95
- ERROR = 'ERROR',
96
- DATA_RETRIEVAL_PARTIALLY_SUCCESSFUL = 'DATA_RETRIEVAL_PARTIALLY_SUCCESSFUL',
97
- DATA_RETRIEVAL_COMPLETE = 'DATA_RETRIEVAL_COMPLETE',
98
- }
146
+ import { DefaultApi } from '@finverse/sdk-typescript';
147
+
148
+ const paymentLinkId = req.query.payment_link_id as string; // from callback URL
149
+ const uniqueReferenceId = req.query.unique_reference_id as string;
150
+
151
+ // Verify uniqueReferenceId matches what you stored
152
+ // if (uniqueReferenceId !== storedUniqueReferenceId) { return error; }
99
153
 
100
154
  const configuration = new Configuration({
101
- basePath: apiHost,
102
- accessToken: loginIdentityToken.access_token
155
+ basePath: apiHost,
156
+ accessToken: customerAccessToken,
103
157
  });
104
- let loginIdentity: AxiosResponse<GetLoginIdentityByIdResponse>;
105
-
106
- // Poll until loginIdentityStatus is ready
107
- for (let i = 0; i < 20; i++) {
108
- loginIdentity = await new LoginIdentityApi(configuration).getLoginIdentity();
109
- const loginIdentityStatus = loginIdentity.data.login_identity.status;
110
- if (
111
- loginIdentityStatus === FinalStatus.ERROR ||
112
- loginIdentityStatus === FinalStatus.DATA_RETRIEVAL_COMPLETE ||
113
- loginIdentityStatus === FinalStatus.DATA_RETRIEVAL_PARTIALLY_SUCCESSFUL
114
- ) { break; }
115
-
116
- await new Promise((resolve) => setTimeout(resolve, 3000));
117
- }
158
+ const defaultApi = new DefaultApi(configuration);
118
159
 
119
- console.log("login identity: " + loginIdentityResp.login_identity)
120
- // get other products (Accounts, Account Numbers, Transactions)
121
- ```
160
+ const POLL_INTERVAL_MS = 2000;
161
+ const POLL_TIMEOUT_MS = 30000;
162
+ const startTime = Date.now();
122
163
 
123
- ### 6. Get Accounts
124
- ```typescript
125
- // Get Accounts
126
- const configuration = new Configuration({ basePath: apiHost, accessToken: loginIdentityToken.access_token });
127
- const accountsRsp = await new LoginIdentityApi(configuration).listAccounts();
164
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
165
+ const getResp = await defaultApi.getPaymentLink(paymentLinkId);
166
+ const sessionStatus = getResp.data.session_status;
128
167
 
129
- console.log("accounts: " + accountsResp.accounts)
130
- ```
168
+ if (sessionStatus === "COMPLETE") {
169
+ // Success – redirect to success page
170
+ // Log getResp.data.payment for payment details
171
+ break;
172
+ }
131
173
 
132
- ### 7. Get Transactions
133
- ```typescript
134
- // Get Transactions with pagination using offset and limit
135
- let offset = 0
136
- while(true) {
137
- const configuration = new Configuration({ basePath: apiHost, accessToken: loginIdentityToken.access_token });
138
- const transactionsResp = await new LoginIdentityApi(configuration).listTransactionsByLoginIdentityId();
139
-
140
- console.log(`total: ${transactionsResp.total_transactions}, transactions: ${transactionsResp.transactions}`)
141
- offset += transactionsResp.transactions.length
142
-
143
- if offset >= transactionsResp.total_transactions {
144
- break
145
- }
174
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
146
175
  }
176
+
177
+ // If loop ended without COMPLETE → timeout, show error page
147
178
  ```
148
179
 
149
- ### 8. Get Statements
150
- ```typescript
151
- // Get Statements metadata
152
- const configuration = new Configuration({ basePath: apiHost, accessToken: loginIdentityToken.access_token });
153
- const statements = await new LoginIdentityApi(configuration).getStatements();
180
+ ---
154
181
 
155
- console.log("statements: " + statementsResp.statements)
182
+ ## Data API
156
183
 
157
- // Get link to statement
158
- // Assuming there is only one statement
159
- const statementId = statements.data.statements[0].id;
184
+ The Data API enables you to retrieve financial data: accounts, transactions, statements, balance history, identity, and income estimates.
160
185
 
161
- // Can download statement from here
162
- const statementResp = await new LoginIdentityApi(configuration).getStatement(statementId, true, {responseType: "arraybuffer"});
163
- writeFileSync("statement.pdf", Buffer.from(statementResp.data));
164
- ```
186
+ ### Key Classes & Methods
187
+
188
+ | Class | Key Methods |
189
+ |-------|-------------|
190
+ | `LoginIdentityApi` | `getLoginIdentity`, `listAccounts`, `getAccount`, `getAccountNumber`, `listTransactionsByLoginIdentityId`, `listTransactionsByAccountId`, `getStatements`, `getStatement`, `getBalanceHistory`, `getIdentity`, `getIncomeEstimateByLoginIdentityId`, `listCardDetails`, ` refreshLoginIdentity` |
191
+
192
+ ### Data Retrieval Flow (End-to-End)
165
193
 
166
- ## Getting started (Payment flow)
194
+ #### 1. Authenticate: Obtain Customer Access Token
167
195
 
168
- ### 1. Authenticate with Finverse API: Obtain Customer Access Token
169
196
  ```typescript
170
- // Obtain these from https://dashboard.finverse.com
171
- const apiHost = "https://api.sandbox.finverse.net"
172
- const clientId = process.env.FINVERSE_CLIENTID
173
- const clientSecret = process.env.FINVERSE_SECRET
174
- const redirectUri = process.env.REDIRECT_URI
197
+ import { Configuration, PublicApi } from '@finverse/sdk-typescript';
198
+
199
+ const apiHost = "https://api.prod.finverse.net";
200
+ const clientId = process.env.FINVERSE_CLIENTID;
201
+ const clientSecret = process.env.FINVERSE_SECRET;
202
+ const redirectUri = process.env.REDIRECT_URI;
175
203
 
176
204
  const configuration = new Configuration({ basePath: apiHost });
177
- // Obtain customer access token
178
205
  const customerTokenResp = await new PublicApi(configuration).generateCustomerAccessToken({
179
- client_id: clientId,
180
- client_secret: clientSecret,
181
- grant_type: 'client_credentials',
206
+ client_id: clientId,
207
+ client_secret: clientSecret,
208
+ grant_type: 'client_credentials',
182
209
  });
183
210
 
184
- const customerAccessToken = customerTokenResp.access_token
211
+ const customerAccessToken = customerTokenResp.data.access_token;
185
212
  ```
186
213
 
187
- ### 2. Create payment instruction
188
- ```typescript
189
- const configuration = new Configuration({ basePath: config.apiHost, accessToken: customerToken.access_token });
190
- const paymentInstruction: CustomerPaymentInstruction = {
191
- type: "DEBIT_AUTHORIZATION",
192
- user_id: "customer_user1",
193
- frequency: "MONTHLY",
194
- start_date: "2022-04-01",
195
- end_date: "2022-12-01",
196
- amount: 1000,
197
- currency: "PHP",
198
- recipient_name: "HOMECREDIT",
199
- recipient_account_id: "Recipient Account Id",
200
- sender_name: "Sender Name",
201
- sender_account_id: "LOAN102345",
202
- remarks: "HOME CREDIT REPAYMENT"
203
- };
204
- const createPaymentInstructionResponse = await new CustomerApi(configuration).createPaymentInstruction(paymentInstruction);
205
-
206
- // createPaymentInstructionResponse.data.payment_instruction_id can be used to retrieve the status
207
- ```
214
+ #### 2. Link New Institution: Obtain Link Token and Link URL
208
215
 
209
- ### 3. Link with payment instruction: Obtain Link Token and Link URL to launch Finverse Link UI
210
216
  ```typescript
211
- // generate a link token
212
-
213
- // reference back to your system userId, finverse does not use this
214
- const userId = "someUserId"
215
- // this will be sent in the redirectUri callback, can be used to identify the state
216
- const state = "someUniqueState"
217
- const configuration = new Configuration({
218
- basePath: apiHost,
219
- accessToken: customerToken.access_token
217
+ import { CustomerApi } from '@finverse/sdk-typescript';
218
+
219
+ const userId = "someUserId";
220
+ const state = "someUniqueState";
221
+ const linkConfig = new Configuration({
222
+ basePath: apiHost,
223
+ accessToken: customerAccessToken,
220
224
  });
221
- const linkTokenResp = await new CustomerApi(configuration).generateLinkToken({
222
- client_id: clientId,
223
- user_id: userId,
224
- redirect_uri: redirectUri,
225
- state: state,
225
+ const linkTokenResp = await new CustomerApi(linkConfig).generateLinkToken({
226
+ client_id: clientId,
227
+ user_id: userId,
228
+ redirect_uri: redirectUri,
229
+ state: state,
226
230
  response_mode: "form_post",
227
231
  response_type: "code",
228
- grant_type: "client_credentials",
229
- payment_instruction_id: createPaymentInstructionResponse.data.payment_instruction_id,
230
- products_requested: "PAYMENTS",
232
+ grant_type: "client_credentials",
231
233
  });
232
234
 
233
- // The linkUrl can be used to initiate Finverse Link
234
- console.log("linkUrl: " + linkTokenResp.link_url)
235
+ console.log("linkUrl:", linkTokenResp.data.link_url);
235
236
  ```
236
237
 
237
- ### 4. Finalize linking: Exchange code for Login Identity Access Token
238
+ #### 3. Finalize Linking: Exchange Code for Login Identity Access Token
239
+
238
240
  ```typescript
239
- // when Finverse Link UI is successful, obtain the code from Finverse Link
240
- // exchange it for a Login Identity Access Token
241
- const code = "obtainAfterLink"
242
- const configuration = new Configuration({
243
- basePath: apiHost,
244
- accessToken: customerToken.access_token
245
- });
246
- const loginIdentityTokenResp = await new LinkApi(configuration).token(
247
- "authorization_code",
248
- code,
249
- clientId,
250
- redirectURI,
241
+ import { LinkApi } from '@finverse/sdk-typescript';
242
+
243
+ const code = "obtainAfterLink"; // from Finverse Link UI callback
244
+ const loginIdentityTokenResp = await new LinkApi(linkConfig).token(
245
+ "authorization_code",
246
+ code,
247
+ clientId,
248
+ redirectUri,
251
249
  );
250
+ const loginIdentityToken = loginIdentityTokenResp.data.access_token;
251
+ ```
252
+
253
+ #### 4. Retrieve data: Get Login Identity
252
254
 
253
- // The loginIdentityToken can be used to retrieve data
254
- const loginIdentityToken = loginIdentityTokenResp.access_token
255
+ ```typescript
256
+ import { LoginIdentityApi } from '@finverse/sdk-typescript';
257
+
258
+ const dataConfig = new Configuration({
259
+ basePath: apiHost,
260
+ accessToken: loginIdentityToken,
261
+ });
262
+ const loginIdentityResp = await new LoginIdentityApi(dataConfig).getLoginIdentity();
263
+ console.log("login identity:", loginIdentityResp.data.login_identity);
255
264
  ```
256
265
 
257
- ### 5. Poll loginIdentityStatus until ready
258
- Alternatively you can use webhook to receive LoginIdentity event.
266
+ #### 5. Poll Login Identity Status Until Ready
259
267
 
260
268
  ```typescript
269
+ import type { AxiosResponse } from 'axios';
270
+ import type { GetLoginIdentityByIdResponse } from '@finverse/sdk-typescript';
271
+
261
272
  enum FinalStatus {
262
- ERROR = 'ERROR',
263
- CONNECTION_COMPLETE= 'CONNECTION_COMPLETE',
273
+ ERROR = 'ERROR',
274
+ DATA_RETRIEVAL_PARTIALLY_SUCCESSFUL = 'DATA_RETRIEVAL_PARTIALLY_SUCCESSFUL',
275
+ DATA_RETRIEVAL_COMPLETE = 'DATA_RETRIEVAL_COMPLETE',
264
276
  }
265
277
 
266
- const configuration = new Configuration({
267
- basePath: apiHost,
268
- accessToken: loginIdentityToken.access_token
269
- });
270
278
  let loginIdentity: AxiosResponse<GetLoginIdentityByIdResponse>;
271
-
272
- // Poll until loginIdentityStatus is ready
273
279
  for (let i = 0; i < 20; i++) {
274
- loginIdentity = await new LoginIdentityApi(configuration).getLoginIdentity();
275
- const loginIdentityStatus = loginIdentity.data.login_identity.status;
276
- if (
277
- loginIdentityStatus === FinalStatus.ERROR ||
278
- loginIdentityStatus === FinalStatus.CONNECTION_COMPLETE
279
- ) { break; }
280
-
281
- await new Promise((resolve) => setTimeout(resolve, 3000));
280
+ loginIdentity = await new LoginIdentityApi(dataConfig).getLoginIdentity();
281
+ const status = loginIdentity.data.login_identity.status;
282
+ if (
283
+ status === FinalStatus.ERROR ||
284
+ status === FinalStatus.DATA_RETRIEVAL_COMPLETE ||
285
+ status === FinalStatus.DATA_RETRIEVAL_PARTIALLY_SUCCESSFUL
286
+ ) {
287
+ break;
288
+ }
289
+ await new Promise((resolve) => setTimeout(resolve, 3000));
282
290
  }
291
+ ```
292
+
293
+ #### 6. Get Accounts
283
294
 
284
- console.log("login identity: " + loginIdentityResp.login_identity)
295
+ ```typescript
296
+ const accountsResp = await new LoginIdentityApi(dataConfig).listAccounts();
297
+ console.log("accounts:", accountsResp.data.accounts);
285
298
  ```
286
299
 
287
- ### 6. Get payment instruction status
300
+ #### 7. Get Transactions (with pagination)
301
+
288
302
  ```typescript
289
- const configuration = new Configuration({ basePath: config.apiHost, accessToken: customerToken.access_token });
290
- const getPaymentInstructionResponse = await new CustomerApi(configuration).getPaymentInstruction(createPaymentInstructionResponse.data.payment_instruction_id);
303
+ let offset = 0;
304
+ while (true) {
305
+ const transactionsResp = await new LoginIdentityApi(dataConfig).listTransactionsByLoginIdentityId(
306
+ offset,
307
+ 500 // limit: default 500, max 1000
308
+ );
309
+ const transactions = transactionsResp.data.transactions ?? [];
310
+ console.log(`total: ${transactionsResp.data.total_transactions}, transactions: ${transactions.length}`);
311
+ offset += transactions.length;
312
+ if (offset >= transactionsResp.data.total_transactions) {
313
+ break;
314
+ }
315
+ }
316
+ ```
291
317
 
292
- console.log("paymentInstruction status: " + getPaymentInstructionResponse.data.payment_instruction.status);
318
+ #### 8. Get Statements
319
+
320
+ ```typescript
321
+ import { writeFileSync } from 'fs';
322
+
323
+ const statementsResp = await new LoginIdentityApi(dataConfig).getStatements();
324
+ const statements = statementsResp.data.statements ?? [];
325
+ console.log("statements:", statements);
326
+
327
+ // Download a statement (assuming at least one exists)
328
+ const statementId = statements[0].id;
329
+ const statementResp = await new LoginIdentityApi(dataConfig).getStatement(
330
+ statementId,
331
+ true,
332
+ { responseType: "arraybuffer" }
333
+ );
334
+ writeFileSync("statement.pdf", Buffer.from(statementResp.data));
293
335
  ```
336
+
337
+ ---
338
+
339
+ ## Best Practices
340
+
341
+ ### Use async/await
342
+
343
+ All SDK methods return Promises. Use `async/await` for cleaner, readable code and to avoid callback hell:
344
+
345
+ ```typescript
346
+ async function fetchAccounts() {
347
+ const config = new Configuration({ basePath: apiHost, accessToken: token });
348
+ const response = await new LoginIdentityApi(config).listAccounts();
349
+ return response.data.accounts;
350
+ }
351
+ ```
352
+
353
+ ### Handle API responses correctly
354
+
355
+ API methods return Axios `AxiosResponse` objects. Access the payload via `.data`:
356
+
357
+ ```typescript
358
+ const tokenResp = await new PublicApi(config).generateCustomerAccessToken({ ... });
359
+ const accessToken = tokenResp.data.access_token; // Correct: use .data
360
+
361
+ const accountsResp = await new LoginIdentityApi(config).listAccounts();
362
+ const accounts = accountsResp.data.accounts; // Correct: use .data
363
+ ```
364
+
365
+ ### Use exported types and interfaces
366
+
367
+ Leverage the SDK's exported types for type safety and better IDE support:
368
+
369
+ ```typescript
370
+ import type {
371
+ Account,
372
+ CreatePaymentRequest,
373
+ GetLoginIdentityByIdResponse,
374
+ } from '@finverse/sdk-typescript';
375
+
376
+ const paymentRequest: CreatePaymentRequest = {
377
+ // ...
378
+ };
379
+ ```
380
+
381
+ ### Handle errors with try/catch
382
+
383
+ Wrap API calls in try/catch blocks. API errors are returned as HTTP error responses; the SDK may throw on network failures or validation errors:
384
+
385
+ ```typescript
386
+ import axios from 'axios';
387
+
388
+ try {
389
+ const response = await new LoginIdentityApi(config).listAccounts();
390
+ return response.data.accounts;
391
+ } catch (error) {
392
+ if (axios.isAxiosError(error) && error.response) {
393
+ console.error("API error:", error.response.status, error.response.data);
394
+ } else {
395
+ throw error;
396
+ }
397
+ }
398
+ ```
399
+
400
+ ### Handle validation errors
401
+
402
+ When required parameters are missing, the SDK throws errors. Wrap API calls in try/catch to handle validation and other failures gracefully.
403
+
404
+ ### Reuse Configuration instances
405
+
406
+ Create a `Configuration` once per token/context and reuse it for multiple API calls:
407
+
408
+ ```typescript
409
+ const config = new Configuration({
410
+ basePath: apiHost,
411
+ accessToken: customerAccessToken,
412
+ });
413
+ const customerApi = new CustomerApi(config);
414
+ const linkTokenResp = await customerApi.generateLinkToken({ ... });
415
+ const paymentResp = await customerApi.createPayment(paymentRequest);
416
+ ```
417
+
418
+ ### Client secret: never commit, server-side only
419
+
420
+ **Never commit your client secret to version control.** The client secret must remain confidential and be used only on the server.
421
+
422
+ - **Server-side only:** This SDK is designed for backend use. Never expose `client_secret` to the browser, mobile apps, or any client-side code.
423
+ - **Use environment variables or a secrets manager:** Load credentials at runtime from secure storage.
424
+ - **Add to `.gitignore`:** Ensure `.env` and any files containing secrets are never committed.
425
+
426
+ ```typescript
427
+ // ✅ Correct: Load from environment (server-side only)
428
+ const clientId = process.env.FINVERSE_CLIENTID;
429
+ const clientSecret = process.env.FINVERSE_SECRET; // Never log, never send to client
430
+ const redirectUri = process.env.REDIRECT_URI;
431
+ ```
432
+
433
+ ### Payment idempotency
434
+
435
+ For payment and mandate operations, use idempotency keys to safely retry requests without creating duplicate payments. If a request fails due to network issues, you can retry with the same key—the API will return the original result instead of creating a duplicate.
436
+
437
+ **Methods that support idempotency keys:** `createPayment`, `createMandate`, `createMandateForExistingSender` (DefaultApi), `createScheduledPayout` (DefaultApi).
438
+
439
+ ```typescript
440
+ import { CustomerApi } from '@finverse/sdk-typescript';
441
+ import crypto from 'crypto';
442
+
443
+ // Generate a unique key per logical operation (e.g., per checkout or mandate setup)
444
+ const idempotencyKey = crypto.randomUUID();
445
+
446
+ // Safe to retry—same key returns same result
447
+ await new CustomerApi(config).createPayment(paymentRequest, idempotencyKey);
448
+ await new CustomerApi(config).createMandate(mandateRequest, idempotencyKey);
449
+ ```
450
+
451
+ **Best practices:**
452
+ - Use one idempotency key per unique payment or mandate.
453
+ - Store the key with your order/mandate record so you can retry with the same key if needed.
454
+
455
+ ---
456
+
457
+ ## Resources for AI Agents
458
+
459
+ For AI agents implementing Finverse integrations, the [Finverse AI repository](https://github.com/finversetech/ai) contains skills, implementation guides, and reference documentation for payment flows, data retrieval, and other Finverse API patterns. Follow this link for detailed integration instructions: https://github.com/finversetech/ai