@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.
- package/README.md +378 -212
- package/dist/api.d.ts +0 -261
- package/dist/api.js +0 -490
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,293 +1,459 @@
|
|
|
1
|
-
# Finverse API -
|
|
2
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
110
|
+
**Setup mode** – For saving payment methods (e.g. Click to Pay). Do **not** pass `amount`:
|
|
111
|
+
|
|
57
112
|
```typescript
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
|
|
155
|
+
basePath: apiHost,
|
|
156
|
+
accessToken: customerAccessToken,
|
|
103
157
|
});
|
|
104
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
160
|
+
const POLL_INTERVAL_MS = 2000;
|
|
161
|
+
const POLL_TIMEOUT_MS = 30000;
|
|
162
|
+
const startTime = Date.now();
|
|
122
163
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
+
## Data API
|
|
156
183
|
|
|
157
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
194
|
+
#### 1. Authenticate: Obtain Customer Access Token
|
|
167
195
|
|
|
168
|
-
### 1. Authenticate with Finverse API: Obtain Customer Access Token
|
|
169
196
|
```typescript
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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(
|
|
222
|
-
|
|
223
|
-
user_id:
|
|
224
|
-
redirect_uri:
|
|
225
|
-
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
|
-
|
|
229
|
-
payment_instruction_id: createPaymentInstructionResponse.data.payment_instruction_id,
|
|
230
|
-
products_requested: "PAYMENTS",
|
|
232
|
+
grant_type: "client_credentials",
|
|
231
233
|
});
|
|
232
234
|
|
|
233
|
-
|
|
234
|
-
console.log("linkUrl: " + linkTokenResp.link_url)
|
|
235
|
+
console.log("linkUrl:", linkTokenResp.data.link_url);
|
|
235
236
|
```
|
|
236
237
|
|
|
237
|
-
|
|
238
|
+
#### 3. Finalize Linking: Exchange Code for Login Identity Access Token
|
|
239
|
+
|
|
238
240
|
```typescript
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const code = "obtainAfterLink"
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
295
|
+
```typescript
|
|
296
|
+
const accountsResp = await new LoginIdentityApi(dataConfig).listAccounts();
|
|
297
|
+
console.log("accounts:", accountsResp.data.accounts);
|
|
285
298
|
```
|
|
286
299
|
|
|
287
|
-
|
|
300
|
+
#### 7. Get Transactions (with pagination)
|
|
301
|
+
|
|
288
302
|
```typescript
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|