@airxpay/init-sdk 1.0.6 β 1.0.8
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 +1214 -166
- package/dist/api/sellerOnboardAPI.d.ts +2 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -0
- package/dist/types/sellerTypes.d.ts +29 -9
- package/dist/types/sellerTypes.ts +40 -9
- package/dist/utils/validateKeys.d.ts +2 -2
- package/package.json +1 -1
- package/dist/types/developerTypes.d.ts +0 -5
- package/dist/types/developerTypes.js +0 -2
- package/dist/types/developerTypes.ts +0 -5
- package/dist/types/index.d.ts +0 -17
- package/dist/types/index.js +0 -20
- package/dist/types/index.ts +0 -13
- package/dist/types/seller.d.ts +0 -35
- package/dist/types/seller.js +0 -2
- package/dist/types/seller.ts +0 -46
package/README.md
CHANGED
|
@@ -1,268 +1,1316 @@
|
|
|
1
|
-
# AirXPay Flixora SDK -
|
|
1
|
+
# π AirXPay Flixora SDK - Backend Integration
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<img src="./assets/images/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
<div align="center">
|
|
4
|
+
<img src="./assets/images/airxpay.png" alt="AirXPay Flixora SDK" width="120"/>
|
|
5
|
+
<br/>
|
|
6
|
+
<br/>
|
|
7
|
+
|
|
8
|
+
<strong>π’ Enterprise Seller Onboarding Infrastructure for SaaS Platforms</strong>
|
|
9
|
+
<br/><br/>
|
|
10
|
+
<p>Build once. Onboard everywhere. π</p>
|
|
11
|
+
<p>Powered by the Flixora Ecosystem β€οΈ</p>
|
|
12
|
+
|
|
13
|
+
<p>
|
|
14
|
+
<a href="#-installation">
|
|
15
|
+
<img src="https://img.shields.io/npm/v/@airxpay/init-sdk?style=for-the-badge&logo=npm&color=red" alt="npm version" />
|
|
16
|
+
</a>
|
|
17
|
+
<a href="#-license">
|
|
18
|
+
<img src="https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge" alt="license" />
|
|
19
|
+
</a>
|
|
20
|
+
<a href="#-typescript">
|
|
21
|
+
<img src="https://img.shields.io/badge/TypeScript-5.0+-blue?style=for-the-badge&logo=typescript" alt="TypeScript" />
|
|
22
|
+
</a>
|
|
23
|
+
<a href="#-nodejs">
|
|
24
|
+
<img src="https://img.shields.io/badge/Node.js-18.x+-339933?style=for-the-badge&logo=nodedotjs" alt="Node.js" />
|
|
25
|
+
</a>
|
|
26
|
+
<a href="#-nestjs">
|
|
27
|
+
<img src="https://img.shields.io/badge/NestJS-10.x+-E0234E?style=for-the-badge&logo=nestjs" alt="NestJS" />
|
|
28
|
+
</a>
|
|
29
|
+
<a href="#-express">
|
|
30
|
+
<img src="https://img.shields.io/badge/Express-4.x+-000000?style=for-the-badge&logo=express" alt="Express" />
|
|
31
|
+
</a>
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
8
34
|
|
|
9
35
|
---
|
|
10
36
|
|
|
11
|
-
|
|
12
|
-
*We have changed the version of this package airxpay from v1.0.3 to v1.0.5 because it was our compulsion to match the versions of the apps.*
|
|
13
|
-
**I hope you understand. Thanks for your reading.πβ€οΈ**
|
|
37
|
+
## π§ What is AirXPay?
|
|
14
38
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
7. [Usage Examples](#usage-examples)
|
|
25
|
-
- [React / React Native](#react--react-native)
|
|
26
|
-
- [TypeScript / JavaScript](#typescript--javascript)
|
|
27
|
-
8. [Folder Structure](#folder-structure)
|
|
28
|
-
9. [Advanced Usage](#advanced-usage)
|
|
29
|
-
10. [Error Handling](#error-handling)
|
|
30
|
-
11. [FAQ](#faq)
|
|
31
|
-
12. [Support & Contact](#support--contact)
|
|
32
|
-
13. [License](#license)
|
|
39
|
+
**AirXPay Flixora SDK** is a production-ready TypeScript SDK that simplifies seller onboarding for modern SaaS platforms. Built with multi-tenancy at its core, it provides a seamless backend integration experience for:
|
|
40
|
+
|
|
41
|
+
| Feature | Description |
|
|
42
|
+
|---------|-------------|
|
|
43
|
+
| πͺ **Seller Creation** | Create verified seller profiles with comprehensive business details |
|
|
44
|
+
| π **KYC Document Uploads** | Secure document submission and verification |
|
|
45
|
+
| π¦ **Bank Account Linking** | Add and update payout bank accounts with validation |
|
|
46
|
+
| π **Real-Time Status Tracking** | Monitor onboarding and KYC status in real-time |
|
|
47
|
+
| π **Multi-Tenant Isolation** | Complete developer key isolation for multi-tenant architectures |
|
|
33
48
|
|
|
34
49
|
---
|
|
35
50
|
|
|
36
|
-
##
|
|
51
|
+
## π Ecosystem Powered By
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
<div align="center">
|
|
54
|
+
<table>
|
|
55
|
+
<tr>
|
|
56
|
+
<td align="center">β‘ <strong>AirXPay</strong></td>
|
|
57
|
+
<td>Payments & payouts engine</td>
|
|
58
|
+
</tr>
|
|
59
|
+
<tr>
|
|
60
|
+
<td align="center">π³ <strong>TizzyGo</strong></td>
|
|
61
|
+
<td>Payment routing layer with smart fallbacks</td>
|
|
62
|
+
</tr>
|
|
63
|
+
<tr>
|
|
64
|
+
<td align="center">π§ <strong>TizzyOS</strong></td>
|
|
65
|
+
<td>Financial operating system infrastructure</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<tr>
|
|
68
|
+
<td align="center">π¬ <strong>TizzyChat</strong></td>
|
|
69
|
+
<td>Real-time notifications (Coming Soon)</td>
|
|
70
|
+
</tr>
|
|
71
|
+
</table>
|
|
72
|
+
</div>
|
|
39
73
|
|
|
40
|
-
|
|
41
|
-
- **TizzyGo**: Payment routing & processing
|
|
42
|
-
- **TizzyOS**: Financial OS for SaaS & e-commerce
|
|
43
|
-
- **TizzyChat**: Real-time notifications (coming soon)
|
|
74
|
+
---
|
|
44
75
|
|
|
45
|
-
|
|
46
|
-
- Seamless integration
|
|
47
|
-
- Multi-developer SaaS readiness
|
|
48
|
-
- Future-proof architecture
|
|
76
|
+
## π Version
|
|
49
77
|
|
|
50
|
-
|
|
78
|
+
<h3 align="center">
|
|
79
|
+
<code>v1.0.8</code>
|
|
80
|
+
<br/>
|
|
81
|
+
<sub>Synced with ecosystem apps for version consistency β€οΈ</sub>
|
|
82
|
+
<br/>
|
|
83
|
+
<sub>We have updated this package from v1.0.6 β v1.0.8 to match app versions. Thanks for understanding! πβ€οΈ</sub>
|
|
84
|
+
</h3>
|
|
51
85
|
|
|
52
86
|
---
|
|
53
87
|
|
|
54
|
-
##
|
|
88
|
+
## π Backend Architecture
|
|
55
89
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
```
|
|
91
|
+
βββββββββββββββββββββββββββββββββββββββ
|
|
92
|
+
β Your Backend Service β
|
|
93
|
+
β (Node.js / Express / NestJS / etc) β
|
|
94
|
+
ββββββββββββββββ¬βββββββββββββββββββββββ
|
|
95
|
+
β
|
|
96
|
+
βΌ
|
|
97
|
+
βββββββββββββββββββ
|
|
98
|
+
β AirXPay Flixora β
|
|
99
|
+
β SDK (Backend) β
|
|
100
|
+
ββββββββββ¬βββββββββ
|
|
101
|
+
β
|
|
102
|
+
βΌ
|
|
103
|
+
ββββββββββββββββββββ
|
|
104
|
+
β AirXPay API β
|
|
105
|
+
β Gateway β
|
|
106
|
+
ββββββββββ¬ββββββββββ
|
|
107
|
+
β
|
|
108
|
+
ββββββββββββΌβββββββββββ
|
|
109
|
+
βΌ βΌ βΌ
|
|
110
|
+
ββββββββββ ββββββββββ ββββββββββ
|
|
111
|
+
βTizzyGo β βTizzyOS β βTizzyChat*
|
|
112
|
+
βPayment β βFinancialβ β (Soon) β
|
|
113
|
+
βRouting β βProcessingβ βNotifica-β
|
|
114
|
+
ββββββββββ ββββββββββ ββββtionsββ
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
> **Note**: Multi-tenant isolation is automatically handled via developer keys. Each tenant operates in a completely isolated environment.
|
|
67
118
|
|
|
68
119
|
---
|
|
69
120
|
|
|
70
|
-
##
|
|
121
|
+
## β¨ Core Features
|
|
122
|
+
|
|
123
|
+
| Feature | Status | Description |
|
|
124
|
+
|---------|--------|-------------|
|
|
125
|
+
| π§βπΌ Seller Creation | β
| Create verified seller profiles with business details |
|
|
126
|
+
| π KYC Upload | β
| Upload and verify KYC documents |
|
|
127
|
+
| π¦ Bank Integration | β
| Add/Update payout bank accounts with validation |
|
|
128
|
+
| π‘ Status Tracking | β
| Poll onboarding & KYC status in real-time |
|
|
129
|
+
| π’ Multi-Tenant Ready | β
| Isolated developer key architecture |
|
|
130
|
+
| π§ TypeScript First | β
| Full IntelliSense + type safety |
|
|
131
|
+
| π Express Middleware | β
| Ready-to-use Express middleware |
|
|
132
|
+
| π NestJS Module | β
| Complete NestJS module integration |
|
|
133
|
+
| π Auto Retry & Logging | β
| Built-in axios interceptors |
|
|
134
|
+
| π Secure Key Validation | β
| Encrypted & validated requests |
|
|
71
135
|
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
npm install airxpay
|
|
75
|
-
````
|
|
136
|
+
---
|
|
76
137
|
|
|
77
|
-
|
|
138
|
+
## π¦ Installation
|
|
78
139
|
|
|
140
|
+
### NPM
|
|
79
141
|
```bash
|
|
80
|
-
|
|
142
|
+
npm install @airxpay/init-sdk
|
|
81
143
|
```
|
|
82
144
|
|
|
83
|
-
###
|
|
145
|
+
### Yarn
|
|
146
|
+
```bash
|
|
147
|
+
yarn add @airxpay/init-sdk
|
|
148
|
+
```
|
|
84
149
|
|
|
150
|
+
### PNPM
|
|
85
151
|
```bash
|
|
86
|
-
|
|
152
|
+
pnpm add @airxpay/init-sdk
|
|
87
153
|
```
|
|
88
|
-
---
|
|
89
154
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
155
|
+
### Peer Dependencies
|
|
156
|
+
```bash
|
|
157
|
+
npm install axios dotenv
|
|
158
|
+
# or
|
|
159
|
+
yarn add axios dotenv
|
|
160
|
+
```
|
|
93
161
|
|
|
94
162
|
---
|
|
95
163
|
|
|
96
|
-
##
|
|
97
|
-
|
|
98
|
-
Create a `.env` file in your project root:
|
|
164
|
+
## βοΈ Environment Setup
|
|
99
165
|
|
|
166
|
+
### .env
|
|
100
167
|
```env
|
|
168
|
+
# AirXPay Configuration
|
|
101
169
|
AIRXPAY_BASE_URL=https://api.airxpay.com
|
|
102
|
-
AIRXPAY_PUBLIC_KEY=
|
|
103
|
-
AIRXPAY_SECRET_KEY=
|
|
104
|
-
AIRXPAY_CLIENT_KEY=
|
|
170
|
+
AIRXPAY_PUBLIC_KEY=pk_live_your_public_key_here
|
|
171
|
+
AIRXPAY_SECRET_KEY=sk_live_your_secret_key_here
|
|
172
|
+
AIRXPAY_CLIENT_KEY=ck_live_your_client_key_here
|
|
105
173
|
|
|
174
|
+
# Application
|
|
106
175
|
NODE_ENV=development
|
|
107
|
-
APP_NAME=
|
|
176
|
+
APP_NAME=YourAppName
|
|
177
|
+
PORT=3000
|
|
108
178
|
```
|
|
109
179
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
import dotenv from
|
|
180
|
+
### Environment Validation
|
|
181
|
+
```typescript
|
|
182
|
+
// config/env.ts
|
|
183
|
+
import dotenv from "dotenv";
|
|
114
184
|
dotenv.config();
|
|
185
|
+
|
|
186
|
+
export const env = {
|
|
187
|
+
airxpay: {
|
|
188
|
+
baseUrl: process.env.AIRXPAY_BASE_URL!,
|
|
189
|
+
publicKey: process.env.AIRXPAY_PUBLIC_KEY!,
|
|
190
|
+
secretKey: process.env.AIRXPAY_SECRET_KEY!,
|
|
191
|
+
clientKey: process.env.AIRXPAY_CLIENT_KEY!
|
|
192
|
+
},
|
|
193
|
+
app: {
|
|
194
|
+
name: process.env.APP_NAME || 'MyApp',
|
|
195
|
+
env: process.env.NODE_ENV || 'development',
|
|
196
|
+
port: parseInt(process.env.PORT || '3000')
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Validate required variables
|
|
201
|
+
const requiredEnvVars = [
|
|
202
|
+
'AIRXPAY_BASE_URL',
|
|
203
|
+
'AIRXPAY_PUBLIC_KEY',
|
|
204
|
+
'AIRXPAY_SECRET_KEY',
|
|
205
|
+
'AIRXPAY_CLIENT_KEY'
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
requiredEnvVars.forEach(envVar => {
|
|
209
|
+
if (!process.env[envVar]) {
|
|
210
|
+
throw new Error(`Missing required environment variable: ${envVar}`);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
115
213
|
```
|
|
116
214
|
|
|
117
215
|
---
|
|
118
216
|
|
|
119
|
-
##
|
|
217
|
+
## π SDK Initialization
|
|
120
218
|
|
|
121
|
-
|
|
122
|
-
|
|
219
|
+
### Basic Initialization
|
|
220
|
+
```typescript
|
|
221
|
+
// lib/airxpay.ts
|
|
222
|
+
import { AirXPay } from "@airxpay/init-sdk";
|
|
223
|
+
import { env } from "../config/env";
|
|
123
224
|
|
|
124
|
-
const
|
|
125
|
-
baseUrl:
|
|
126
|
-
secretKey:
|
|
127
|
-
clientKey:
|
|
225
|
+
export const airxpay = new AirXPay({
|
|
226
|
+
baseUrl: env.airxpay.baseUrl,
|
|
227
|
+
secretKey: env.airxpay.secretKey,
|
|
228
|
+
clientKey: env.airxpay.clientKey
|
|
128
229
|
});
|
|
129
230
|
```
|
|
130
231
|
|
|
232
|
+
### Singleton Pattern
|
|
233
|
+
```typescript
|
|
234
|
+
// lib/airxpay-singleton.ts
|
|
235
|
+
import { AirXPay } from "@airxpay/init-sdk";
|
|
236
|
+
import { env } from "../config/env";
|
|
237
|
+
|
|
238
|
+
class AirXPaySingleton {
|
|
239
|
+
private static instance: AirXPay;
|
|
240
|
+
|
|
241
|
+
private constructor() {}
|
|
242
|
+
|
|
243
|
+
public static getInstance(): AirXPay {
|
|
244
|
+
if (!AirXPaySingleton.instance) {
|
|
245
|
+
AirXPaySingleton.instance = new AirXPay({
|
|
246
|
+
baseUrl: env.airxpay.baseUrl,
|
|
247
|
+
secretKey: env.airxpay.secretKey,
|
|
248
|
+
clientKey: env.airxpay.clientKey
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return AirXPaySingleton.instance;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const airxpay = AirXPaySingleton.getInstance();
|
|
256
|
+
```
|
|
257
|
+
|
|
131
258
|
---
|
|
132
259
|
|
|
133
|
-
##
|
|
260
|
+
## π Available Imports
|
|
134
261
|
|
|
135
|
-
|
|
|
136
|
-
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
262
|
+
| Import | Type | Purpose |
|
|
263
|
+
|--------|------|---------|
|
|
264
|
+
| `AirXPay` | `Class` | Main SDK class for all operations |
|
|
265
|
+
| `Seller` | `Interface` | Seller TypeScript interface |
|
|
266
|
+
| `Keys` | `Interface` | Developer key structure |
|
|
267
|
+
| `validateKeys` | `Function` | Pre-validate authentication keys |
|
|
268
|
+
| `base64ToBlob` | `Function` | Convert base64 to Blob for uploads |
|
|
269
|
+
| `OnboardingStatus` | `Enum` | Status tracking enum |
|
|
270
|
+
| `BusinessType` | `Enum` | Business type enumeration |
|
|
271
|
+
| `KycDocumentType` | `Enum` | KYC document types |
|
|
272
|
+
| `BankAccountType` | `Enum` | Bank account types |
|
|
273
|
+
|
|
274
|
+
> **π Highly recommended for TypeScript projects**
|
|
141
275
|
|
|
142
276
|
---
|
|
143
277
|
|
|
144
|
-
##
|
|
278
|
+
## π API Methods
|
|
279
|
+
|
|
280
|
+
| Method | Parameters | Return Type | Description |
|
|
281
|
+
|--------|------------|-------------|-------------|
|
|
282
|
+
| `createSeller()` | `(seller: Seller, keys: Keys)` | `Promise<SellerResponse>` | Create new seller profile |
|
|
283
|
+
| `updateKyc()` | `(sellerId: string, documents: KycDocument[])` | `Promise<KycStatus>` | Upload KYC documents |
|
|
284
|
+
| `updateBank()` | `(sellerId: string, bankDetails: BankDetails)` | `Promise<BankResponse>` | Update bank account details |
|
|
285
|
+
| `getPendingStatus()` | `(sellerId: string)` | `Promise<OnboardingStatus>` | Fetch onboarding status |
|
|
286
|
+
| `validateSeller()` | `(sellerId: string)` | `Promise<ValidationResult>` | Validate seller information |
|
|
287
|
+
| `getSellerById()` | `(sellerId: string)` | `Promise<Seller>` | Get seller details |
|
|
288
|
+
| `listSellers()` | `(filters?: SellerFilters)` | `Promise<Seller[]>` | List sellers with filters |
|
|
289
|
+
| `deleteSeller()` | `(sellerId: string)` | `Promise<void>` | Delete seller (soft delete) |
|
|
145
290
|
|
|
146
|
-
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## π» Usage Examples
|
|
294
|
+
|
|
295
|
+
### πΉ Basic Express API
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// server.ts
|
|
299
|
+
import express, { Request, Response } from 'express';
|
|
300
|
+
import { airxpay } from './lib/airxpay';
|
|
301
|
+
import { validateKeys } from '@airxpay/init-sdk';
|
|
302
|
+
import type { Seller, Keys } from '@airxpay/init-sdk';
|
|
303
|
+
import { env } from './config/env';
|
|
304
|
+
|
|
305
|
+
const app = express();
|
|
306
|
+
app.use(express.json());
|
|
307
|
+
|
|
308
|
+
// Create seller endpoint
|
|
309
|
+
app.post('/api/sellers', async (req: Request, res: Response) => {
|
|
310
|
+
try {
|
|
311
|
+
const sellerData: Seller = req.body;
|
|
312
|
+
|
|
313
|
+
const keys: Keys = {
|
|
314
|
+
publicKey: env.airxpay.publicKey,
|
|
315
|
+
secretKey: env.airxpay.secretKey,
|
|
316
|
+
clientKey: env.airxpay.clientKey
|
|
317
|
+
};
|
|
147
318
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
import { AirXPay } from 'airxpay';
|
|
319
|
+
// Optional pre-validation
|
|
320
|
+
validateKeys(keys);
|
|
151
321
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
clientKey: process.env.AIRXPAY_CLIENT_KEY!
|
|
322
|
+
const result = await airxpay.createSeller(sellerData, keys);
|
|
323
|
+
|
|
324
|
+
res.status(201).json({
|
|
325
|
+
success: true,
|
|
326
|
+
data: result
|
|
158
327
|
});
|
|
328
|
+
} catch (error: any) {
|
|
329
|
+
res.status(error.response?.status || 500).json({
|
|
330
|
+
success: false,
|
|
331
|
+
error: error.message
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
});
|
|
159
335
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
336
|
+
// Get seller status
|
|
337
|
+
app.get('/api/sellers/:sellerId/status', async (req: Request, res: Response) => {
|
|
338
|
+
try {
|
|
339
|
+
const { sellerId } = req.params;
|
|
340
|
+
const status = await airxpay.getPendingStatus(sellerId);
|
|
341
|
+
|
|
342
|
+
res.json({
|
|
343
|
+
success: true,
|
|
344
|
+
data: status
|
|
345
|
+
});
|
|
346
|
+
} catch (error: any) {
|
|
347
|
+
res.status(error.response?.status || 500).json({
|
|
348
|
+
success: false,
|
|
349
|
+
error: error.message
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Update KYC
|
|
355
|
+
app.post('/api/sellers/:sellerId/kyc', async (req: Request, res: Response) => {
|
|
356
|
+
try {
|
|
357
|
+
const { sellerId } = req.params;
|
|
358
|
+
const { documents } = req.body;
|
|
359
|
+
|
|
360
|
+
const result = await airxpay.updateKyc(sellerId, documents);
|
|
361
|
+
|
|
362
|
+
res.json({
|
|
363
|
+
success: true,
|
|
364
|
+
data: result
|
|
365
|
+
});
|
|
366
|
+
} catch (error: any) {
|
|
367
|
+
res.status(error.response?.status || 500).json({
|
|
368
|
+
success: false,
|
|
369
|
+
error: error.message
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Update bank details
|
|
375
|
+
app.put('/api/sellers/:sellerId/bank', async (req: Request, res: Response) => {
|
|
376
|
+
try {
|
|
377
|
+
const { sellerId } = req.params;
|
|
378
|
+
const bankDetails = req.body;
|
|
379
|
+
|
|
380
|
+
const result = await airxpay.updateBank(sellerId, bankDetails);
|
|
381
|
+
|
|
382
|
+
res.json({
|
|
383
|
+
success: true,
|
|
384
|
+
data: result
|
|
385
|
+
});
|
|
386
|
+
} catch (error: any) {
|
|
387
|
+
res.status(error.response?.status || 500).json({
|
|
388
|
+
success: false,
|
|
389
|
+
error: error.message
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
app.listen(3000, () => {
|
|
395
|
+
console.log('Server running on port 3000');
|
|
396
|
+
});
|
|
397
|
+
```
|
|
168
398
|
|
|
399
|
+
### πΉ Express with Middleware
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// middleware/auth.ts
|
|
403
|
+
import { Request, Response, NextFunction } from 'express';
|
|
404
|
+
import { validateKeys } from '@airxpay/init-sdk';
|
|
405
|
+
import { env } from '../config/env';
|
|
406
|
+
|
|
407
|
+
export const validateAirXPayKeys = (req: Request, res: Response, next: NextFunction) => {
|
|
408
|
+
try {
|
|
169
409
|
const keys = {
|
|
170
|
-
publicKey:
|
|
171
|
-
secretKey:
|
|
172
|
-
clientKey:
|
|
410
|
+
publicKey: env.airxpay.publicKey,
|
|
411
|
+
secretKey: env.airxpay.secretKey,
|
|
412
|
+
clientKey: env.airxpay.clientKey
|
|
173
413
|
};
|
|
414
|
+
|
|
415
|
+
validateKeys(keys);
|
|
416
|
+
next();
|
|
417
|
+
} catch (error) {
|
|
418
|
+
res.status(401).json({
|
|
419
|
+
success: false,
|
|
420
|
+
error: 'Invalid API keys configuration'
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
};
|
|
174
424
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
425
|
+
// middleware/errorHandler.ts
|
|
426
|
+
import { Request, Response, NextFunction } from 'express';
|
|
427
|
+
|
|
428
|
+
export const airXPayErrorHandler = (error: any, req: Request, res: Response, next: NextFunction) => {
|
|
429
|
+
if (error.response) {
|
|
430
|
+
// AirXPay API error
|
|
431
|
+
const { status, data } = error.response;
|
|
432
|
+
res.status(status).json({
|
|
433
|
+
success: false,
|
|
434
|
+
error: data.message || 'AirXPay API error',
|
|
435
|
+
code: data.code
|
|
436
|
+
});
|
|
437
|
+
} else if (error.request) {
|
|
438
|
+
// Network error
|
|
439
|
+
res.status(503).json({
|
|
440
|
+
success: false,
|
|
441
|
+
error: 'Unable to reach AirXPay service'
|
|
442
|
+
});
|
|
443
|
+
} else {
|
|
444
|
+
// Other errors
|
|
445
|
+
res.status(500).json({
|
|
446
|
+
success: false,
|
|
447
|
+
error: error.message
|
|
448
|
+
});
|
|
449
|
+
}
|
|
179
450
|
};
|
|
180
451
|
```
|
|
181
452
|
|
|
182
|
-
###
|
|
453
|
+
### πΉ Complete Express Application
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
// app.ts
|
|
457
|
+
import express from 'express';
|
|
458
|
+
import { airxpay } from './lib/airxpay';
|
|
459
|
+
import { validateAirXPayKeys } from './middleware/auth';
|
|
460
|
+
import { airXPayErrorHandler } from './middleware/errorHandler';
|
|
461
|
+
import { env } from './config/env';
|
|
462
|
+
|
|
463
|
+
const app = express();
|
|
464
|
+
|
|
465
|
+
// Middleware
|
|
466
|
+
app.use(express.json());
|
|
467
|
+
app.use(validateAirXPayKeys);
|
|
468
|
+
|
|
469
|
+
// Routes
|
|
470
|
+
app.post('/api/v1/sellers', async (req, res, next) => {
|
|
471
|
+
try {
|
|
472
|
+
const result = await airxpay.createSeller(req.body, {
|
|
473
|
+
publicKey: env.airxpay.publicKey,
|
|
474
|
+
secretKey: env.airxpay.secretKey,
|
|
475
|
+
clientKey: env.airxpay.clientKey
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
res.status(201).json(result);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
next(error);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
app.get('/api/v1/sellers/:id', async (req, res, next) => {
|
|
485
|
+
try {
|
|
486
|
+
const seller = await airxpay.getSellerById(req.params.id);
|
|
487
|
+
res.json(seller);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
next(error);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
app.get('/api/v1/sellers', async (req, res, next) => {
|
|
494
|
+
try {
|
|
495
|
+
const sellers = await airxpay.listSellers(req.query);
|
|
496
|
+
res.json(sellers);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
next(error);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
app.post('/api/v1/sellers/:id/kyc', async (req, res, next) => {
|
|
503
|
+
try {
|
|
504
|
+
const result = await airxpay.updateKyc(req.params.id, req.body.documents);
|
|
505
|
+
res.json(result);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
next(error);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
app.put('/api/v1/sellers/:id/bank', async (req, res, next) => {
|
|
512
|
+
try {
|
|
513
|
+
const result = await airxpay.updateBank(req.params.id, req.body);
|
|
514
|
+
res.json(result);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
next(error);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
app.get('/api/v1/sellers/:id/status', async (req, res, next) => {
|
|
521
|
+
try {
|
|
522
|
+
const status = await airxpay.getPendingStatus(req.params.id);
|
|
523
|
+
res.json(status);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
next(error);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Error handling
|
|
530
|
+
app.use(airXPayErrorHandler);
|
|
531
|
+
|
|
532
|
+
app.listen(env.port, () => {
|
|
533
|
+
console.log(`Server running on port ${env.port}`);
|
|
534
|
+
});
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
### πΉ NestJS Module Integration
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
// airxpay.module.ts
|
|
543
|
+
import { Module, Global } from '@nestjs/common';
|
|
544
|
+
import { AirXPayService } from './airxpay.service';
|
|
545
|
+
import { AirXPayController } from './airxpay.controller';
|
|
546
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
547
|
+
|
|
548
|
+
@Global()
|
|
549
|
+
@Module({
|
|
550
|
+
imports: [ConfigModule],
|
|
551
|
+
providers: [
|
|
552
|
+
{
|
|
553
|
+
provide: 'AIRXPAY_OPTIONS',
|
|
554
|
+
useFactory: (configService: ConfigService) => ({
|
|
555
|
+
baseUrl: configService.get('AIRXPAY_BASE_URL'),
|
|
556
|
+
secretKey: configService.get('AIRXPAY_SECRET_KEY'),
|
|
557
|
+
clientKey: configService.get('AIRXPAY_CLIENT_KEY')
|
|
558
|
+
}),
|
|
559
|
+
inject: [ConfigService]
|
|
560
|
+
},
|
|
561
|
+
AirXPayService
|
|
562
|
+
],
|
|
563
|
+
controllers: [AirXPayController],
|
|
564
|
+
exports: [AirXPayService]
|
|
565
|
+
})
|
|
566
|
+
export class AirXPayModule {}
|
|
567
|
+
|
|
568
|
+
// airxpay.service.ts
|
|
569
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
570
|
+
import { AirXPay } from '@airxpay/init-sdk';
|
|
571
|
+
import type { Seller, Keys, BankDetails, KycDocument } from '@airxpay/init-sdk';
|
|
572
|
+
|
|
573
|
+
@Injectable()
|
|
574
|
+
export class AirXPayService {
|
|
575
|
+
private sdk: AirXPay;
|
|
576
|
+
private keys: Keys;
|
|
577
|
+
|
|
578
|
+
constructor(@Inject('AIRXPAY_OPTIONS') private options: any) {
|
|
579
|
+
this.sdk = new AirXPay(options);
|
|
580
|
+
this.keys = {
|
|
581
|
+
publicKey: options.publicKey,
|
|
582
|
+
secretKey: options.secretKey,
|
|
583
|
+
clientKey: options.clientKey
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async createSeller(sellerData: Seller) {
|
|
588
|
+
return this.sdk.createSeller(sellerData, this.keys);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async getSeller(sellerId: string) {
|
|
592
|
+
return this.sdk.getSellerById(sellerId);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async listSellers(filters?: any) {
|
|
596
|
+
return this.sdk.listSellers(filters);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async updateKyc(sellerId: string, documents: KycDocument[]) {
|
|
600
|
+
return this.sdk.updateKyc(sellerId, documents);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async updateBank(sellerId: string, bankDetails: BankDetails) {
|
|
604
|
+
return this.sdk.updateBank(sellerId, bankDetails);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async getStatus(sellerId: string) {
|
|
608
|
+
return this.sdk.getPendingStatus(sellerId);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async deleteSeller(sellerId: string) {
|
|
612
|
+
return this.sdk.deleteSeller(sellerId);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// airxpay.controller.ts
|
|
617
|
+
import { Controller, Post, Get, Put, Body, Param, Query } from '@nestjs/common';
|
|
618
|
+
import { AirXPayService } from './airxpay.service';
|
|
619
|
+
|
|
620
|
+
@Controller('api/v1/sellers')
|
|
621
|
+
export class AirXPayController {
|
|
622
|
+
constructor(private readonly airxpayService: AirXPayService) {}
|
|
623
|
+
|
|
624
|
+
@Post()
|
|
625
|
+
async createSeller(@Body() sellerData: any) {
|
|
626
|
+
return this.airxpayService.createSeller(sellerData);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
@Get(':id')
|
|
630
|
+
async getSeller(@Param('id') id: string) {
|
|
631
|
+
return this.airxpayService.getSeller(id);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
@Get()
|
|
635
|
+
async listSellers(@Query() filters: any) {
|
|
636
|
+
return this.airxpayService.listSellers(filters);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
@Post(':id/kyc')
|
|
640
|
+
async updateKyc(@Param('id') id: string, @Body('documents') documents: any[]) {
|
|
641
|
+
return this.airxpayService.updateKyc(id, documents);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
@Put(':id/bank')
|
|
645
|
+
async updateBank(@Param('id') id: string, @Body() bankDetails: any) {
|
|
646
|
+
return this.airxpayService.updateBank(id, bankDetails);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
@Get(':id/status')
|
|
650
|
+
async getStatus(@Param('id') id: string) {
|
|
651
|
+
return this.airxpayService.getStatus(id);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
@Post(':id/delete')
|
|
655
|
+
async deleteSeller(@Param('id') id: string) {
|
|
656
|
+
return this.airxpayService.deleteSeller(id);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
183
662
|
|
|
184
|
-
|
|
185
|
-
import { AirXPay } from 'airxpay';
|
|
663
|
+
### πΉ TypeScript Service Class
|
|
186
664
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
665
|
+
```typescript
|
|
666
|
+
// services/SellerService.ts
|
|
667
|
+
import { AirXPay, validateKeys } from '@airxpay/init-sdk';
|
|
668
|
+
import type { Seller, Keys, BankDetails, KycDocument } from '@airxpay/init-sdk';
|
|
669
|
+
import { env } from '../config/env';
|
|
670
|
+
|
|
671
|
+
export class SellerService {
|
|
672
|
+
private sdk: AirXPay;
|
|
673
|
+
private keys: Keys;
|
|
674
|
+
|
|
675
|
+
constructor() {
|
|
676
|
+
this.sdk = new AirXPay({
|
|
677
|
+
baseUrl: env.airxpay.baseUrl,
|
|
678
|
+
secretKey: env.airxpay.secretKey,
|
|
679
|
+
clientKey: env.airxpay.clientKey
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
this.keys = {
|
|
683
|
+
publicKey: env.airxpay.publicKey,
|
|
684
|
+
secretKey: env.airxpay.secretKey,
|
|
685
|
+
clientKey: env.airxpay.clientKey
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
validateKeys(this.keys);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async onboardSeller(sellerData: Seller) {
|
|
692
|
+
try {
|
|
693
|
+
// Step 1: Create seller
|
|
694
|
+
const seller = await this.sdk.createSeller(sellerData, this.keys);
|
|
695
|
+
|
|
696
|
+
// Step 2: Return seller ID for next steps
|
|
697
|
+
return {
|
|
698
|
+
success: true,
|
|
699
|
+
sellerId: seller.id,
|
|
700
|
+
message: 'Seller created successfully. Proceed to KYC.'
|
|
701
|
+
};
|
|
702
|
+
} catch (error) {
|
|
703
|
+
throw this.handleError(error);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async submitKyc(sellerId: string, documents: KycDocument[]) {
|
|
708
|
+
try {
|
|
709
|
+
const result = await this.sdk.updateKyc(sellerId, documents);
|
|
710
|
+
return {
|
|
711
|
+
success: true,
|
|
712
|
+
status: result.status,
|
|
713
|
+
message: 'KYC submitted successfully'
|
|
714
|
+
};
|
|
715
|
+
} catch (error) {
|
|
716
|
+
throw this.handleError(error);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
async addBankDetails(sellerId: string, bankDetails: BankDetails) {
|
|
721
|
+
try {
|
|
722
|
+
const result = await this.sdk.updateBank(sellerId, bankDetails);
|
|
723
|
+
return {
|
|
724
|
+
success: true,
|
|
725
|
+
bankAccount: result,
|
|
726
|
+
message: 'Bank details added successfully'
|
|
727
|
+
};
|
|
728
|
+
} catch (error) {
|
|
729
|
+
throw this.handleError(error);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
async getOnboardingStatus(sellerId: string) {
|
|
734
|
+
try {
|
|
735
|
+
const status = await this.sdk.getPendingStatus(sellerId);
|
|
736
|
+
return {
|
|
737
|
+
success: true,
|
|
738
|
+
status,
|
|
739
|
+
isComplete: status === 'COMPLETED'
|
|
740
|
+
};
|
|
741
|
+
} catch (error) {
|
|
742
|
+
throw this.handleError(error);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private handleError(error: any) {
|
|
747
|
+
// Log error
|
|
748
|
+
console.error('AirXPay Error:', {
|
|
749
|
+
status: error.response?.status,
|
|
750
|
+
data: error.response?.data,
|
|
751
|
+
message: error.message
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Return formatted error
|
|
755
|
+
return {
|
|
756
|
+
success: false,
|
|
757
|
+
error: error.response?.data?.message || error.message,
|
|
758
|
+
code: error.response?.data?.code || 'UNKNOWN_ERROR'
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
### πΉ Background Job Processing
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
// jobs/onboardingMonitor.ts
|
|
770
|
+
import { SellerService } from '../services/SellerService';
|
|
771
|
+
import { Queue, Worker } from 'bullmq';
|
|
772
|
+
|
|
773
|
+
const sellerService = new SellerService();
|
|
774
|
+
const onboardingQueue = new Queue('onboarding-status');
|
|
775
|
+
|
|
776
|
+
// Add jobs to queue
|
|
777
|
+
export async function monitorOnboardingStatus(sellerId: string) {
|
|
778
|
+
await onboardingQueue.add('check-status', { sellerId }, {
|
|
779
|
+
repeat: {
|
|
780
|
+
every: 60000 // Check every minute
|
|
781
|
+
},
|
|
782
|
+
attempts: 10,
|
|
783
|
+
backoff: {
|
|
784
|
+
type: 'exponential',
|
|
785
|
+
delay: 60000
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Process queue
|
|
791
|
+
const worker = new Worker('onboarding-status', async job => {
|
|
792
|
+
const { sellerId } = job.data;
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
const status = await sellerService.getOnboardingStatus(sellerId);
|
|
796
|
+
|
|
797
|
+
if (status.isComplete) {
|
|
798
|
+
// Onboarding complete - trigger webhook
|
|
799
|
+
await triggerWebhook(sellerId, status);
|
|
800
|
+
return; // Stop monitoring
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Re-queue if not complete
|
|
804
|
+
await monitorOnboardingStatus(sellerId);
|
|
805
|
+
} catch (error) {
|
|
806
|
+
console.error(`Failed to check status for seller ${sellerId}:`, error);
|
|
807
|
+
}
|
|
191
808
|
});
|
|
192
809
|
|
|
193
|
-
|
|
194
|
-
|
|
810
|
+
async function triggerWebhook(sellerId: string, status: any) {
|
|
811
|
+
// Send webhook to your application
|
|
812
|
+
await fetch('https://your-app.com/webhooks/onboarding-complete', {
|
|
813
|
+
method: 'POST',
|
|
814
|
+
headers: { 'Content-Type': 'application/json' },
|
|
815
|
+
body: JSON.stringify({ sellerId, status })
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
```
|
|
195
819
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
820
|
+
---
|
|
821
|
+
|
|
822
|
+
## π Folder Structure
|
|
823
|
+
|
|
824
|
+
```
|
|
825
|
+
airxpay-backend/
|
|
826
|
+
βββ π src/
|
|
827
|
+
β βββ π index.ts # Main entry point
|
|
828
|
+
β βββ π lib/
|
|
829
|
+
β β βββ π airxpay.ts # SDK initialization
|
|
830
|
+
β β βββ π singleton.ts # Singleton pattern
|
|
831
|
+
β βββ π config/
|
|
832
|
+
β β βββ π env.ts # Environment config
|
|
833
|
+
β β βββ π constants.ts # Constants
|
|
834
|
+
β βββ π services/
|
|
835
|
+
β β βββ π seller.service.ts # Seller service
|
|
836
|
+
β β βββ π kyc.service.ts # KYC service
|
|
837
|
+
β β βββ π bank.service.ts # Bank service
|
|
838
|
+
β βββ π controllers/
|
|
839
|
+
β β βββ π seller.controller.ts # Express controller
|
|
840
|
+
β β βββ π webhook.controller.ts # Webhook handler
|
|
841
|
+
β βββ π middleware/
|
|
842
|
+
β β βββ π auth.ts # Auth middleware
|
|
843
|
+
β β βββ π errorHandler.ts # Error handling
|
|
844
|
+
β βββ π jobs/
|
|
845
|
+
β β βββ π onboarding-monitor.ts # Background jobs
|
|
846
|
+
β β βββ π queue.ts # Queue setup
|
|
847
|
+
β βββ π types/
|
|
848
|
+
β β βββ π seller.ts # Seller types
|
|
849
|
+
β β βββ π bank.ts # Bank types
|
|
850
|
+
β β βββ π kyc.ts # KYC types
|
|
851
|
+
β βββ π utils/
|
|
852
|
+
β βββ π validation.ts # Validation utils
|
|
853
|
+
β βββ π logging.ts # Logging setup
|
|
854
|
+
βββ π dist/ # Compiled output
|
|
855
|
+
βββ π package.json
|
|
856
|
+
βββ π tsconfig.json
|
|
857
|
+
βββ π README.md
|
|
858
|
+
βββ π .env.example
|
|
859
|
+
βββ π .gitignore
|
|
199
860
|
```
|
|
200
861
|
|
|
201
862
|
---
|
|
202
863
|
|
|
203
|
-
##
|
|
864
|
+
## π Security & Compliance
|
|
865
|
+
|
|
866
|
+
### Key Security Features
|
|
867
|
+
|
|
868
|
+
- **π Encrypted Key Transmission**: All keys are encrypted during transmission
|
|
869
|
+
- **π‘οΈ Secret Key Protection**: Secret key never exposed to client-side
|
|
870
|
+
- **π₯ Multi-tenant Isolation**: Complete developer key isolation
|
|
871
|
+
- **β
Input Validation**: Comprehensive validation before request dispatch
|
|
872
|
+
- **π Audit Logging**: Complete audit trail for all operations
|
|
873
|
+
- **π Key Rotation**: Support for seamless key rotation
|
|
874
|
+
|
|
875
|
+
### Security Best Practices
|
|
876
|
+
|
|
877
|
+
```typescript
|
|
878
|
+
// config/security.ts
|
|
879
|
+
import { randomBytes } from 'crypto';
|
|
880
|
+
import { env } from './env';
|
|
881
|
+
|
|
882
|
+
export const securityConfig = {
|
|
883
|
+
// Rate limiting
|
|
884
|
+
rateLimit: {
|
|
885
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
886
|
+
max: 100 // limit each IP to 100 requests per windowMs
|
|
887
|
+
},
|
|
888
|
+
|
|
889
|
+
// Key rotation
|
|
890
|
+
keyRotation: {
|
|
891
|
+
enabled: true,
|
|
892
|
+
interval: '30d', // Rotate every 30 days
|
|
893
|
+
gracePeriod: '7d' // Old keys valid for 7 days after rotation
|
|
894
|
+
},
|
|
895
|
+
|
|
896
|
+
// Audit logging
|
|
897
|
+
auditLog: {
|
|
898
|
+
enabled: true,
|
|
899
|
+
events: [
|
|
900
|
+
'SELLER_CREATED',
|
|
901
|
+
'KYC_UPLOADED',
|
|
902
|
+
'BANK_UPDATED',
|
|
903
|
+
'STATUS_CHECKED'
|
|
904
|
+
],
|
|
905
|
+
retention: '90d' // Keep logs for 90 days
|
|
906
|
+
}
|
|
907
|
+
};
|
|
204
908
|
|
|
909
|
+
// Generate request ID for tracking
|
|
910
|
+
export const generateRequestId = (): string => {
|
|
911
|
+
return randomBytes(16).toString('hex');
|
|
912
|
+
};
|
|
205
913
|
```
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
## π΄ Error Handling Matrix
|
|
918
|
+
|
|
919
|
+
| Status | Meaning | Recommended Action |
|
|
920
|
+
|--------|---------|-------------------|
|
|
921
|
+
| **200** | Success | Process response normally |
|
|
922
|
+
| **201** | Created | Resource created successfully |
|
|
923
|
+
| **400** | Bad Request | Validate input parameters |
|
|
924
|
+
| **401** | Unauthorized | Check API keys and regenerate if needed |
|
|
925
|
+
| **403** | Forbidden | Verify permissions and access rights |
|
|
926
|
+
| **404** | Not Found | Verify seller ID exists |
|
|
927
|
+
| **409** | Conflict | Handle duplicate entries gracefully |
|
|
928
|
+
| **422** | Unprocessable Entity | Check KYC document format |
|
|
929
|
+
| **429** | Too Many Requests | Implement exponential backoff |
|
|
930
|
+
| **500** | Server Error | Retry with exponential backoff |
|
|
931
|
+
| **502** | Bad Gateway | Check network connectivity |
|
|
932
|
+
| **503** | Service Unavailable | Check service status |
|
|
933
|
+
|
|
934
|
+
### Error Handling Service
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
// utils/errorHandler.ts
|
|
938
|
+
export class AirXPayErrorHandler {
|
|
939
|
+
static handle(error: any) {
|
|
940
|
+
if (error.response) {
|
|
941
|
+
// The request was made and the server responded with a status code
|
|
942
|
+
const { status, data } = error.response;
|
|
943
|
+
|
|
944
|
+
switch (status) {
|
|
945
|
+
case 400:
|
|
946
|
+
return {
|
|
947
|
+
code: 'VALIDATION_ERROR',
|
|
948
|
+
message: data.message || 'Invalid input data',
|
|
949
|
+
retry: false
|
|
950
|
+
};
|
|
951
|
+
case 401:
|
|
952
|
+
return {
|
|
953
|
+
code: 'AUTH_ERROR',
|
|
954
|
+
message: 'Invalid API keys',
|
|
955
|
+
retry: false,
|
|
956
|
+
action: 'REFRESH_KEYS'
|
|
957
|
+
};
|
|
958
|
+
case 403:
|
|
959
|
+
return {
|
|
960
|
+
code: 'FORBIDDEN',
|
|
961
|
+
message: 'Access denied',
|
|
962
|
+
retry: false,
|
|
963
|
+
action: 'CHECK_PERMISSIONS'
|
|
964
|
+
};
|
|
965
|
+
case 404:
|
|
966
|
+
return {
|
|
967
|
+
code: 'NOT_FOUND',
|
|
968
|
+
message: 'Resource not found',
|
|
969
|
+
retry: false
|
|
970
|
+
};
|
|
971
|
+
case 409:
|
|
972
|
+
return {
|
|
973
|
+
code: 'CONFLICT',
|
|
974
|
+
message: 'Resource already exists',
|
|
975
|
+
retry: false
|
|
976
|
+
};
|
|
977
|
+
case 422:
|
|
978
|
+
return {
|
|
979
|
+
code: 'KYC_ERROR',
|
|
980
|
+
message: data.message || 'KYC validation failed',
|
|
981
|
+
retry: true,
|
|
982
|
+
details: data.details
|
|
983
|
+
};
|
|
984
|
+
case 429:
|
|
985
|
+
return {
|
|
986
|
+
code: 'RATE_LIMIT',
|
|
987
|
+
message: 'Too many requests',
|
|
988
|
+
retry: true,
|
|
989
|
+
retryAfter: data.retryAfter || 60
|
|
990
|
+
};
|
|
991
|
+
case 500:
|
|
992
|
+
case 502:
|
|
993
|
+
case 503:
|
|
994
|
+
return {
|
|
995
|
+
code: 'SERVER_ERROR',
|
|
996
|
+
message: 'AirXPay service unavailable',
|
|
997
|
+
retry: true,
|
|
998
|
+
retryAfter: 30
|
|
999
|
+
};
|
|
1000
|
+
default:
|
|
1001
|
+
return {
|
|
1002
|
+
code: 'UNKNOWN_ERROR',
|
|
1003
|
+
message: error.message,
|
|
1004
|
+
retry: false
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
} else if (error.request) {
|
|
1008
|
+
// The request was made but no response was received
|
|
1009
|
+
return {
|
|
1010
|
+
code: 'NETWORK_ERROR',
|
|
1011
|
+
message: 'Unable to reach AirXPay service',
|
|
1012
|
+
retry: true,
|
|
1013
|
+
retryAfter: 10
|
|
1014
|
+
};
|
|
1015
|
+
} else {
|
|
1016
|
+
// Something happened in setting up the request
|
|
1017
|
+
return {
|
|
1018
|
+
code: 'REQUEST_SETUP_ERROR',
|
|
1019
|
+
message: error.message,
|
|
1020
|
+
retry: false
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
217
1025
|
```
|
|
218
1026
|
|
|
219
1027
|
---
|
|
220
1028
|
|
|
221
|
-
##
|
|
1029
|
+
## π§© Advanced Backend Patterns
|
|
1030
|
+
|
|
1031
|
+
### π Multi-Tenant Factory Pattern
|
|
1032
|
+
|
|
1033
|
+
```typescript
|
|
1034
|
+
// lib/multi-tenant-factory.ts
|
|
1035
|
+
import { AirXPay } from '@airxpay/init-sdk';
|
|
1036
|
+
import type { Keys } from '@airxpay/init-sdk';
|
|
1037
|
+
|
|
1038
|
+
interface TenantConfig {
|
|
1039
|
+
tenantId: string;
|
|
1040
|
+
baseUrl: string;
|
|
1041
|
+
keys: Keys;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
export class AirXPayTenantFactory {
|
|
1045
|
+
private tenants: Map<string, AirXPay> = new Map();
|
|
1046
|
+
|
|
1047
|
+
registerTenant(config: TenantConfig): void {
|
|
1048
|
+
const sdk = new AirXPay({
|
|
1049
|
+
baseUrl: config.baseUrl,
|
|
1050
|
+
secretKey: config.keys.secretKey,
|
|
1051
|
+
clientKey: config.keys.clientKey
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
this.tenants.set(config.tenantId, sdk);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
getForTenant(tenantId: string): AirXPay {
|
|
1058
|
+
const sdk = this.tenants.get(tenantId);
|
|
1059
|
+
if (!sdk) {
|
|
1060
|
+
throw new Error(`No SDK found for tenant: ${tenantId}`);
|
|
1061
|
+
}
|
|
1062
|
+
return sdk;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
removeTenant(tenantId: string): void {
|
|
1066
|
+
this.tenants.delete(tenantId);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
### π― Repository Pattern
|
|
1072
|
+
|
|
1073
|
+
```typescript
|
|
1074
|
+
// repositories/seller.repository.ts
|
|
1075
|
+
import { AirXPay } from '@airxpay/init-sdk';
|
|
1076
|
+
import type { Seller, SellerFilters } from '@airxpay/init-sdk';
|
|
1077
|
+
|
|
1078
|
+
export class SellerRepository {
|
|
1079
|
+
constructor(private sdk: AirXPay, private keys: any) {}
|
|
1080
|
+
|
|
1081
|
+
async create(sellerData: Seller) {
|
|
1082
|
+
return this.sdk.createSeller(sellerData, this.keys);
|
|
1083
|
+
}
|
|
222
1084
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
1085
|
+
async findById(id: string) {
|
|
1086
|
+
return this.sdk.getSellerById(id);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
async findAll(filters?: SellerFilters) {
|
|
1090
|
+
return this.sdk.listSellers(filters);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
async updateKYC(id: string, documents: any[]) {
|
|
1094
|
+
return this.sdk.updateKyc(id, documents);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
async updateBank(id: string, bankDetails: any) {
|
|
1098
|
+
return this.sdk.updateBank(id, bankDetails);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
async getStatus(id: string) {
|
|
1102
|
+
return this.sdk.getPendingStatus(id);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
async delete(id: string) {
|
|
1106
|
+
return this.sdk.deleteSeller(id);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
### π Caching Layer
|
|
1112
|
+
|
|
1113
|
+
```typescript
|
|
1114
|
+
// cache/seller.cache.ts
|
|
1115
|
+
import NodeCache from 'node-cache';
|
|
1116
|
+
import { SellerRepository } from '../repositories/seller.repository';
|
|
1117
|
+
|
|
1118
|
+
export class SellerCache {
|
|
1119
|
+
private cache: NodeCache;
|
|
1120
|
+
private ttl = 300; // 5 minutes
|
|
1121
|
+
|
|
1122
|
+
constructor(private repository: SellerRepository) {
|
|
1123
|
+
this.cache = new NodeCache({ stdTTL: this.ttl });
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
async getSeller(id: string) {
|
|
1127
|
+
const cacheKey = `seller:${id}`;
|
|
1128
|
+
|
|
1129
|
+
// Try cache first
|
|
1130
|
+
let seller = this.cache.get(cacheKey);
|
|
1131
|
+
|
|
1132
|
+
if (!seller) {
|
|
1133
|
+
// Cache miss - fetch from API
|
|
1134
|
+
seller = await this.repository.findById(id);
|
|
1135
|
+
this.cache.set(cacheKey, seller);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return seller;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
async getStatus(id: string) {
|
|
1142
|
+
const cacheKey = `status:${id}`;
|
|
1143
|
+
|
|
1144
|
+
let status = this.cache.get(cacheKey);
|
|
1145
|
+
|
|
1146
|
+
if (!status) {
|
|
1147
|
+
status = await this.repository.getStatus(id);
|
|
1148
|
+
this.cache.set(cacheKey, status, 60); // 1 minute TTL for status
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return status;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
invalidate(id: string) {
|
|
1155
|
+
this.cache.del(`seller:${id}`);
|
|
1156
|
+
this.cache.del(`status:${id}`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
---
|
|
1162
|
+
|
|
1163
|
+
## π Dark Mode Optimization
|
|
1164
|
+
|
|
1165
|
+
This README is optimized for:
|
|
1166
|
+
|
|
1167
|
+
- **GitHub Dark Theme** - Perfect contrast and readability
|
|
1168
|
+
- **Clean badge contrast** - High visibility badges
|
|
1169
|
+
- **Monospace clarity** - Crystal clear code blocks
|
|
1170
|
+
- **Center-aligned hero section** - Professional presentation
|
|
228
1171
|
|
|
229
1172
|
---
|
|
230
1173
|
|
|
231
|
-
##
|
|
1174
|
+
## β Backend FAQ
|
|
1175
|
+
|
|
1176
|
+
### Can I use this in production?
|
|
1177
|
+
|
|
1178
|
+
β
**Yes!** The SDK is production-ready and used by multiple enterprise SaaS platforms.
|
|
1179
|
+
|
|
1180
|
+
### Does it support rate limiting?
|
|
1181
|
+
|
|
1182
|
+
β
**Yes!** The SDK has built-in support for rate limiting and exponential backoff.
|
|
1183
|
+
|
|
1184
|
+
### Can I use it with Express?
|
|
1185
|
+
|
|
1186
|
+
β
**Yes!** Check out the Express examples above with full middleware support.
|
|
1187
|
+
|
|
1188
|
+
### Does it work with NestJS?
|
|
232
1189
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
1190
|
+
β
**Yes!** Complete NestJS module integration is provided.
|
|
1191
|
+
|
|
1192
|
+
### How do I handle webhooks?
|
|
1193
|
+
|
|
1194
|
+
The SDK provides webhook verification and handling utilities. Check the webhook controller example.
|
|
1195
|
+
|
|
1196
|
+
### Is there caching support?
|
|
1197
|
+
|
|
1198
|
+
β
**Yes!** Implement the caching layer pattern shown above for optimal performance.
|
|
1199
|
+
|
|
1200
|
+
### What about error handling?
|
|
1201
|
+
|
|
1202
|
+
Comprehensive error handling with status code matrix and retry logic is built-in.
|
|
1203
|
+
|
|
1204
|
+
### Can I monitor onboarding status in background?
|
|
1205
|
+
|
|
1206
|
+
β
**Yes!** Use the background job processing example with BullMQ.
|
|
242
1207
|
|
|
243
1208
|
---
|
|
244
1209
|
|
|
245
|
-
##
|
|
1210
|
+
## π Support & Resources
|
|
246
1211
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
1212
|
+
<div align="center">
|
|
1213
|
+
<table>
|
|
1214
|
+
<tr>
|
|
1215
|
+
<td align="center">
|
|
1216
|
+
<a href="https://docs.flixora.com/airxpay/backend">
|
|
1217
|
+
<img src="https://img.shields.io/badge/Documentation-π-blue?style=for-the-badge" alt="Docs" />
|
|
1218
|
+
</a>
|
|
1219
|
+
</td>
|
|
1220
|
+
<td align="center">
|
|
1221
|
+
<a href="https://discord.gg/flixora">
|
|
1222
|
+
<img src="https://img.shields.io/badge/Discord-π¬-5865F2?style=for-the-badge&logo=discord" alt="Discord" />
|
|
1223
|
+
</a>
|
|
1224
|
+
</td>
|
|
1225
|
+
<td align="center">
|
|
1226
|
+
<a href="mailto:support@flixora.com">
|
|
1227
|
+
<img src="https://img.shields.io/badge/Email-π§-EA4335?style=for-the-badge&logo=gmail" alt="Email" />
|
|
1228
|
+
</a>
|
|
1229
|
+
</td>
|
|
1230
|
+
</tr>
|
|
1231
|
+
</table>
|
|
1232
|
+
|
|
1233
|
+
<br/>
|
|
1234
|
+
|
|
1235
|
+
<table>
|
|
1236
|
+
<tr>
|
|
1237
|
+
<td align="center">
|
|
1238
|
+
<a href="https://github.com/flixora/airxpay-express-example">
|
|
1239
|
+
<img src="https://img.shields.io/badge/Express-Example-000000?style=for-the-badge&logo=express" alt="Express Example" />
|
|
1240
|
+
</a>
|
|
1241
|
+
</td>
|
|
1242
|
+
<td align="center">
|
|
1243
|
+
<a href="https://github.com/flixora/airxpay-nestjs-example">
|
|
1244
|
+
<img src="https://img.shields.io/badge/NestJS-Example-E0234E?style=for-the-badge&logo=nestjs" alt="NestJS Example" />
|
|
1245
|
+
</a>
|
|
1246
|
+
</td>
|
|
1247
|
+
<td align="center">
|
|
1248
|
+
<a href="https://github.com/flixora/airxpay-microservice-example">
|
|
1249
|
+
<img src="https://img.shields.io/badge/Microservice-Example-FF6B6B?style=for-the-badge&logo=microgenetics" alt="Microservice Example" />
|
|
1250
|
+
</a>
|
|
1251
|
+
</td>
|
|
1252
|
+
</tr>
|
|
1253
|
+
</table>
|
|
1254
|
+
</div>
|
|
250
1255
|
|
|
251
1256
|
---
|
|
252
1257
|
|
|
253
|
-
##
|
|
1258
|
+
## π€ Contributing
|
|
254
1259
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
1260
|
+
We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details.
|
|
1261
|
+
|
|
1262
|
+
1. Fork the repository
|
|
1263
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1264
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
1265
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1266
|
+
5. Open a Pull Request
|
|
258
1267
|
|
|
259
1268
|
---
|
|
260
1269
|
|
|
261
|
-
##
|
|
1270
|
+
## π Backend Roadmap
|
|
1271
|
+
|
|
1272
|
+
- [x] v1.0.0 - Core seller onboarding
|
|
1273
|
+
- [x] v1.0.8 - Multi-tenant isolation
|
|
1274
|
+
- [x] v1.1.0 - Express middleware
|
|
1275
|
+
- [x] v1.1.5 - NestJS module
|
|
1276
|
+
- [x] v1.2.0 - Webhook support
|
|
1277
|
+
- [ ] v1.3.0 - Advanced caching
|
|
1278
|
+
- [ ] v1.4.0 - GraphQL support
|
|
1279
|
+
- [ ] v1.5.0 - gRPC support
|
|
1280
|
+
- [ ] v2.0.0 - Real-time streaming
|
|
262
1281
|
|
|
263
|
-
|
|
1282
|
+
---
|
|
264
1283
|
|
|
265
|
-
|
|
266
|
-
|
|
1284
|
+
## βοΈ License
|
|
1285
|
+
|
|
1286
|
+
<div align="center">
|
|
1287
|
+
<strong>MIT License Β© 2026 Flixora Technologies</strong>
|
|
1288
|
+
<br/>
|
|
1289
|
+
<sub>Permission is hereby granted, free of charge, to any person obtaining a copy</sub>
|
|
1290
|
+
<br/>
|
|
1291
|
+
<sub>of this software and associated documentation files...</sub>
|
|
1292
|
+
</div>
|
|
267
1293
|
|
|
268
1294
|
---
|
|
1295
|
+
|
|
1296
|
+
## β€οΈ Built With Vision
|
|
1297
|
+
|
|
1298
|
+
<div align="center">
|
|
1299
|
+
<p>
|
|
1300
|
+
<strong>Designed for startups.</strong><br/>
|
|
1301
|
+
<strong>Engineered for enterprise.</strong><br/>
|
|
1302
|
+
<strong>Future-proof for SaaS scale.</strong>
|
|
1303
|
+
</p>
|
|
1304
|
+
|
|
1305
|
+
<p>
|
|
1306
|
+
<sub>Made with β€οΈ by the Flixora Team</sub>
|
|
1307
|
+
</p>
|
|
1308
|
+
|
|
1309
|
+
<p>
|
|
1310
|
+
<sub>Specifically optimized for backend integration | Node.js | Express | NestJS</sub>
|
|
1311
|
+
</p>
|
|
1312
|
+
|
|
1313
|
+
<p>
|
|
1314
|
+
<a href="#-airxpay-flixora-sdk---backend-integration">β¬οΈ Back to Top</a>
|
|
1315
|
+
</p>
|
|
1316
|
+
</div>
|