bazik-sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # Changelog
2
+
3
+ ## 1.0.1 (2026-02-28)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bazik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ <p align="center">
2
+ <img src="logo.svg" alt="Bazik SDK" width="320" />
3
+ </p>
4
+
5
+ <h3 align="center">Unofficial JavaScript SDK for the Bazik API</h3>
6
+
7
+ <p align="center">
8
+ MonCash & NatCash payments, transfers, and wallet management for Haiti.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://bazik.io/docs/endpoints">Documentation</a> ·
13
+ <a href="#quick-start">Quick Start</a> ·
14
+ <a href="#api-reference">API Reference</a> ·
15
+ <a href="https://github.com/bazik-io/bazik-sdk-js/issues">Report Bug</a>
16
+ </p>
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ - **Zero dependencies** — Uses native `fetch` (Node.js 18+)
23
+ - **ESM & CommonJS** — Works with `import` and `require` out of the box
24
+ - **Automatic token management** — Handles auth token lifecycle, refresh, and retry
25
+ - **Full TypeScript support** — Complete `.d.ts` type definitions included
26
+ - **Input validation** — Catches errors before they hit the API
27
+ - **Structured errors** — Typed error classes for every failure mode
28
+ - **MonCash payments** — Create, verify, and poll payment status
29
+ - **MonCash & NatCash transfers** — Send money to wallets directly
30
+ - **Wallet management** — Check balance, get fee quotes
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install bazik-sdk
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```javascript
41
+ // ESM
42
+ import { Bazik } from "bazik-sdk";
43
+
44
+ // CommonJS
45
+ const { Bazik } = require("bazik-sdk");
46
+
47
+ const bazik = new Bazik({
48
+ userID: "bzk_c5b754a0_1757383229",
49
+ secretKey: "sk_5b0ff521b331c73db55313dc82f17cab",
50
+ });
51
+
52
+ // Authentication happens automatically on first API call.
53
+ // You can also authenticate explicitly:
54
+ await bazik.authenticate();
55
+ ```
56
+
57
+ ### Accept a Payment
58
+
59
+ ```javascript
60
+ // 1. Create the payment
61
+ const payment = await bazik.payments.create({
62
+ gdes: 1284.0,
63
+ successUrl: "https://mysite.com/success",
64
+ errorUrl: "https://mysite.com/error",
65
+ description: "iPhone Pro Max",
66
+ referenceId: "ORDER-001",
67
+ customerFirstName: "Franck",
68
+ customerLastName: "Jean",
69
+ customerEmail: "franck@example.com",
70
+ webhookUrl: "https://mysite.com/webhook",
71
+ });
72
+
73
+ // 2. Redirect customer to MonCash payment page
74
+ console.log("Redirect →", payment.redirectUrl);
75
+
76
+ // 3. Verify the payment (after redirect or via webhook)
77
+ const status = await bazik.payments.verify(payment.orderId);
78
+ console.log("Status:", status.status); // "successful" | "pending" | "failed"
79
+ ```
80
+
81
+ ### Send Money (Transfer)
82
+
83
+ ```javascript
84
+ // Check recipient wallet first
85
+ const customer = await bazik.transfers.checkCustomer("37123456");
86
+ console.log("KYC:", customer.customerStatus.type);
87
+
88
+ // Get a fee quote
89
+ const quote = await bazik.transfers.getQuote(500, "moncash");
90
+ console.log(`Fee: ${quote.fee} HTG | Total: ${quote.total_cost} HTG`);
91
+
92
+ // Send via MonCash
93
+ const transfer = await bazik.transfers.moncash({
94
+ gdes: 500,
95
+ wallet: "47556677",
96
+ customerFirstName: "Melissa",
97
+ customerLastName: "Francois",
98
+ description: "Salary payment",
99
+ });
100
+
101
+ console.log("Transaction:", transfer.transaction_id);
102
+
103
+ // Or send via NatCash
104
+ const natTransfer = await bazik.transfers.natcash({
105
+ gdes: 50,
106
+ wallet: "44556677",
107
+ customerFirstName: "Marie",
108
+ customerLastName: "Pierre",
109
+ });
110
+ ```
111
+
112
+ ### Check Balance
113
+
114
+ ```javascript
115
+ const wallet = await bazik.wallet.getBalance();
116
+ console.log(`Available: ${wallet.available} ${wallet.currency}`);
117
+ console.log(`Reserved: ${wallet.reserved} ${wallet.currency}`);
118
+ ```
119
+
120
+ ## API Reference
121
+
122
+ ### `new Bazik(config)`
123
+
124
+ | Parameter | Type | Default | Description |
125
+ |-----------|------|---------|-------------|
126
+ | `userID` | `string` | *required* | Your Bazik user ID |
127
+ | `secretKey` | `string` | *required* | Your secret key |
128
+ | `baseURL` | `string` | `https://api.bazik.io` | API base URL |
129
+ | `autoRefresh` | `boolean` | `true` | Auto-refresh token before expiry |
130
+ | `timeout` | `number` | `30000` | Request timeout (ms) |
131
+ | `onTokenRefresh` | `function` | — | Callback when token refreshes |
132
+
133
+ ### `bazik.payments`
134
+
135
+ | Method | Description |
136
+ |--------|-------------|
137
+ | `.create(params)` | Create a MonCash payment (max 75,000 HTG) |
138
+ | `.verify(orderId)` | Get payment status by order ID |
139
+ | `.waitForCompletion(orderId, opts?)` | Poll until payment resolves |
140
+ | `.withdraw(params)` | Send money to a MonCash wallet |
141
+ | `.getBalance()` | Get account balance |
142
+
143
+ ### `bazik.transfers`
144
+
145
+ | Method | Description |
146
+ |--------|-------------|
147
+ | `.checkCustomer(wallet)` | Check MonCash wallet KYC status |
148
+ | `.moncash(params)` | Create a MonCash transfer |
149
+ | `.natcash(params)` | Create a NatCash transfer |
150
+ | `.getStatus(transactionId)` | Get transfer status |
151
+ | `.getQuote(amount, provider)` | Get fee quote before transfer |
152
+
153
+ ### `bazik.wallet`
154
+
155
+ | Method | Description |
156
+ |--------|-------------|
157
+ | `.getBalance()` | Get wallet balance (available + reserved) |
158
+
159
+ ## Error Handling
160
+
161
+ The SDK provides typed error classes for precise error handling:
162
+
163
+ ```javascript
164
+ import {
165
+ Bazik,
166
+ BazikError, // Base error
167
+ BazikAuthError, // 401 — Invalid credentials
168
+ BazikValidationError, // 400 — Invalid input
169
+ BazikInsufficientFundsError, // 402 — Not enough balance
170
+ BazikRateLimitError, // 429 — Too many requests
171
+ } from "bazik-sdk";
172
+
173
+ try {
174
+ await bazik.payments.create({ gdes: 500 });
175
+ } catch (err) {
176
+ if (err instanceof BazikInsufficientFundsError) {
177
+ console.error("Top up your account!");
178
+ } else if (err instanceof BazikAuthError) {
179
+ console.error("Check your credentials.");
180
+ } else if (err instanceof BazikValidationError) {
181
+ console.error("Invalid input:", err.details);
182
+ } else if (err instanceof BazikRateLimitError) {
183
+ console.error("Slow down — retry later.");
184
+ } else {
185
+ console.error("Unexpected:", err.message);
186
+ }
187
+ }
188
+ ```
189
+
190
+ Every error has these properties:
191
+
192
+ | Property | Type | Description |
193
+ |----------|------|-------------|
194
+ | `message` | `string` | Human-readable description |
195
+ | `status` | `number \| null` | HTTP status code |
196
+ | `code` | `string \| null` | Machine-readable error code |
197
+ | `details` | `any` | Additional context |
198
+
199
+ ## Polling Payment Status
200
+
201
+ Instead of using webhooks, you can poll for payment completion:
202
+
203
+ ```javascript
204
+ try {
205
+ const result = await bazik.payments.waitForCompletion(payment.orderId, {
206
+ intervalMs: 5000, // check every 5s
207
+ timeoutMs: 120000, // give up after 2min
208
+ });
209
+ console.log("Final status:", result.status);
210
+ } catch (err) {
211
+ if (err.code === "timeout") {
212
+ console.log("Customer hasn't completed payment yet.");
213
+ }
214
+ }
215
+ ```
216
+
217
+ ## Testing
218
+
219
+ ```bash
220
+ node --test tests/
221
+ ```
222
+
223
+ No external test framework required — uses Node.js built-in test runner.
224
+
225
+ ## Requirements
226
+
227
+ - Node.js >= 18.0.0 (for native `fetch`)
228
+
229
+ ## Links
230
+
231
+ - [Bazik Documentation](https://bazik.io/docs/endpoints)
232
+ - [Bazik Dashboard](https://bazik.io)
233
+ - [API Status](https://bazik.io)
234
+
235
+ ## License
236
+
237
+ MIT — see [LICENSE](LICENSE) for details.
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "bazik-sdk",
3
+ "version": "1.0.1",
4
+ "description": "Unofficial JavaScript/Node.js SDK for the Bazik API — MonCash & NatCash payments, transfers, and wallet management for Haiti.",
5
+ "main": "src/index.js",
6
+ "module": "src/index.mjs",
7
+ "types": "src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./src/index.d.ts",
12
+ "default": "./src/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./src/index.d.ts",
16
+ "default": "./src/index.js"
17
+ }
18
+ }
19
+ },
20
+ "scripts": {
21
+ "test": "node --test tests/bazik.test.js",
22
+ "test:verbose": "node --test --test-reporter spec tests/bazik.test.js",
23
+ "lint": "echo 'No linter configured'",
24
+ "example": "node examples/quick-start.js",
25
+ "release": "release-it"
26
+ },
27
+ "keywords": [
28
+ "bazik",
29
+ "moncash",
30
+ "natcash",
31
+ "haiti",
32
+ "payment",
33
+ "gateway",
34
+ "api",
35
+ "fintech",
36
+ "mobile-money",
37
+ "gourdes",
38
+ "htg",
39
+ "digicel",
40
+ "transfer",
41
+ "wallet"
42
+ ],
43
+ "author": "Bazik <support@bazik.io>",
44
+ "license": "MIT",
45
+ "homepage": "https://bazik.io",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/josephjoberno/basik-sdk"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/josephjoberno/basik-sdk/issues"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public",
58
+ "provenance": true
59
+ },
60
+ "files": [
61
+ "src/",
62
+ "README.md",
63
+ "LICENSE",
64
+ "CHANGELOG.md"
65
+ ],
66
+ "devDependencies": {
67
+ "@commitlint/cli": "^20.4.2",
68
+ "@commitlint/config-conventional": "^20.4.2",
69
+ "@release-it/conventional-changelog": "^10.0.5",
70
+ "husky": "^9.1.7",
71
+ "release-it": "^19.2.4"
72
+ }
73
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Bazik SDK — TypeScript Definitions
3
+ * @see https://bazik.io/docs/endpoints
4
+ */
5
+
6
+ // ─── Configuration ───────────────────────────────────────────────────────────
7
+
8
+ export interface BazikConfig {
9
+ /** Your Bazik user ID (e.g. "bzk_c5b754a0_1757383229") */
10
+ userID: string;
11
+ /** Your secret key */
12
+ secretKey: string;
13
+ /** API base URL (default: "https://api.bazik.io") */
14
+ baseURL?: string;
15
+ /** Automatically refresh token before expiry (default: true) */
16
+ autoRefresh?: boolean;
17
+ /** Request timeout in ms (default: 30000) */
18
+ timeout?: number;
19
+ /** Callback fired when token is refreshed */
20
+ onTokenRefresh?: (token: string) => void;
21
+ }
22
+
23
+ // ─── Auth ────────────────────────────────────────────────────────────────────
24
+
25
+ export interface AuthResponse {
26
+ success: boolean;
27
+ token: string;
28
+ user_id: string;
29
+ expires_at: number;
30
+ message: string;
31
+ }
32
+
33
+ // ─── Payments ────────────────────────────────────────────────────────────────
34
+
35
+ export interface CreatePaymentParams {
36
+ /** Amount in Gourdes (max 75,000) */
37
+ gdes: number;
38
+ /** User identifier */
39
+ userID?: string;
40
+ successUrl?: string;
41
+ errorUrl?: string;
42
+ description?: string;
43
+ referenceId?: string;
44
+ customerFirstName?: string;
45
+ customerLastName?: string;
46
+ customerEmail?: string;
47
+ webhookUrl?: string;
48
+ metadata?: Record<string, unknown>;
49
+ }
50
+
51
+ export interface PaymentResponse {
52
+ orderId: string;
53
+ redirectUrl: string;
54
+ status: "pending" | "successful" | "failed" | "cancelled";
55
+ gourdes: number;
56
+ referenceId: string;
57
+ environment: "sandbox" | "production";
58
+ sender: string;
59
+ receiver: string;
60
+ customerFullName: string;
61
+ successUrl: string;
62
+ errorUrl: string;
63
+ userID: string;
64
+ transactionType: string;
65
+ metadata: Record<string, unknown>;
66
+ payment: {
67
+ mode: string;
68
+ path: string;
69
+ payment_token: { expired: string; created: string; token: string };
70
+ timestamp: number;
71
+ status: number;
72
+ httpStatusCode: number;
73
+ };
74
+ }
75
+
76
+ export interface PaymentVerification {
77
+ orderId: string;
78
+ referenceId: string;
79
+ status: "pending" | "successful" | "failed" | "cancelled";
80
+ amount: number;
81
+ currency: string;
82
+ createdAt: string;
83
+ updatedAt: string;
84
+ metadata: {
85
+ description?: string;
86
+ customerEmail?: string;
87
+ customerName?: string;
88
+ [key: string]: unknown;
89
+ };
90
+ }
91
+
92
+ export interface WithdrawParams {
93
+ /** Amount in HTG */
94
+ gdes: number;
95
+ /** Recipient phone (8 or 11 digits) */
96
+ wallet: string;
97
+ customerFirstName: string;
98
+ customerLastName: string;
99
+ description?: string;
100
+ referenceId?: string;
101
+ customerEmail?: string;
102
+ webhookUrl?: string;
103
+ }
104
+
105
+ export interface WithdrawResponse {
106
+ transaction_id: string;
107
+ status: "pending" | "completed" | "failed";
108
+ provider: "moncash";
109
+ amount: number;
110
+ fees: number;
111
+ total: number;
112
+ currency: string;
113
+ wallet: string;
114
+ recipient: { first_name: string; last_name: string };
115
+ description: string;
116
+ referenceId: string;
117
+ customerEmail: string;
118
+ webhookUrl: string;
119
+ created_at: string;
120
+ environment: "sandbox" | "production";
121
+ message: string;
122
+ }
123
+
124
+ export interface BalanceResponse {
125
+ available: number;
126
+ reserved: number;
127
+ currency: string;
128
+ environment: "sandbox" | "production";
129
+ last_updated: string;
130
+ }
131
+
132
+ export interface WaitOptions {
133
+ /** Polling interval in ms (default: 5000) */
134
+ intervalMs?: number;
135
+ /** Max wait time in ms (default: 300000) */
136
+ timeoutMs?: number;
137
+ }
138
+
139
+ // ─── Transfers ───────────────────────────────────────────────────────────────
140
+
141
+ export interface TransferParams {
142
+ gdes: number;
143
+ wallet: string;
144
+ customerFirstName: string;
145
+ customerLastName: string;
146
+ description?: string;
147
+ referenceId?: string;
148
+ customerEmail?: string;
149
+ webhookUrl?: string;
150
+ }
151
+
152
+ export interface TransferResponse {
153
+ transaction_id: string;
154
+ status: "pending" | "completed" | "failed";
155
+ provider: "moncash" | "natcash";
156
+ amount: number;
157
+ fees: number;
158
+ total: number;
159
+ currency: string;
160
+ wallet: string;
161
+ recipient: { first_name: string; last_name: string };
162
+ description: string;
163
+ referenceId: string;
164
+ customerEmail: string;
165
+ webhookUrl: string;
166
+ created_at: string;
167
+ environment: "sandbox" | "production";
168
+ message: string;
169
+ }
170
+
171
+ export interface TransferStatusResponse {
172
+ type: "transfer.succeeded" | "transfer.failed";
173
+ transactionId: string;
174
+ status: "successful" | "processing" | "failed" | "cancelled";
175
+ amount: number;
176
+ fees: number;
177
+ total: number;
178
+ currency: string;
179
+ wallet: string;
180
+ description: string;
181
+ recipient: { firstName: string; lastName: string };
182
+ referenceId: string;
183
+ failureReason: string | null;
184
+ timestamp: string;
185
+ provider: "moncash" | "natcash";
186
+ environment: "sandbox" | "production";
187
+ }
188
+
189
+ export interface CustomerStatusResponse {
190
+ customerStatus: {
191
+ type: string;
192
+ status: string[];
193
+ };
194
+ timestamp: number;
195
+ status: number;
196
+ environment: "sandbox" | "production";
197
+ }
198
+
199
+ export interface QuoteResponse {
200
+ delivery_amount: number;
201
+ fee: number;
202
+ total_cost: number;
203
+ currency: string;
204
+ provider: "moncash" | "natcash";
205
+ fee_percentage: number;
206
+ timestamp: string;
207
+ environment: "sandbox" | "production";
208
+ }
209
+
210
+ // ─── Sub-modules ─────────────────────────────────────────────────────────────
211
+
212
+ export declare class Payments {
213
+ create(params: CreatePaymentParams): Promise<PaymentResponse>;
214
+ verify(orderId: string): Promise<PaymentVerification>;
215
+ waitForCompletion(orderId: string, options?: WaitOptions): Promise<PaymentVerification>;
216
+ withdraw(params: WithdrawParams): Promise<WithdrawResponse>;
217
+ getBalance(): Promise<BalanceResponse>;
218
+ }
219
+
220
+ export declare class Transfers {
221
+ checkCustomer(wallet: string): Promise<CustomerStatusResponse>;
222
+ moncash(params: TransferParams): Promise<TransferResponse>;
223
+ natcash(params: TransferParams): Promise<TransferResponse>;
224
+ getStatus(transactionId: string): Promise<TransferStatusResponse>;
225
+ getQuote(amount: number, provider: "moncash" | "natcash"): Promise<QuoteResponse>;
226
+ }
227
+
228
+ export declare class Wallet {
229
+ getBalance(): Promise<BalanceResponse>;
230
+ }
231
+
232
+ // ─── Main Client ─────────────────────────────────────────────────────────────
233
+
234
+ export declare class Bazik {
235
+ constructor(config: BazikConfig);
236
+
237
+ /** MonCash payment operations */
238
+ readonly payments: Payments;
239
+ /** MonCash & NatCash transfer operations */
240
+ readonly transfers: Transfers;
241
+ /** Wallet balance operations */
242
+ readonly wallet: Wallet;
243
+
244
+ /** Authenticate and obtain an access token */
245
+ authenticate(): Promise<AuthResponse>;
246
+ /** Check if current token is still valid */
247
+ isTokenValid(): boolean;
248
+ /** Get a valid token (auto-refreshes if needed) */
249
+ getToken(): Promise<string>;
250
+ }
251
+
252
+ // ─── Errors ──────────────────────────────────────────────────────────────────
253
+
254
+ export declare class BazikError extends Error {
255
+ status: number | null;
256
+ code: string | null;
257
+ details: unknown;
258
+ constructor(message: string, status?: number, code?: string, details?: unknown);
259
+ }
260
+
261
+ export declare class BazikAuthError extends BazikError {}
262
+ export declare class BazikValidationError extends BazikError {}
263
+ export declare class BazikInsufficientFundsError extends BazikError {}
264
+ export declare class BazikRateLimitError extends BazikError {}
265
+
266
+ export default Bazik;
package/src/index.js ADDED
@@ -0,0 +1,692 @@
1
+ /**
2
+ * Bazik SDK for JavaScript/Node.js
3
+ * Unofficial SDK for the Bazik API — MonCash & NatCash payments, transfers, and wallet management.
4
+ *
5
+ * @see https://bazik.io/docs/endpoints
6
+ * @version 1.0.0
7
+ * @license MIT
8
+ */
9
+
10
+ "use strict";
11
+
12
+ // ─── Constants ───────────────────────────────────────────────────────────────
13
+
14
+ const DEFAULT_BASE_URL = "https://api.bazik.io";
15
+ const TOKEN_REFRESH_MARGIN_MS = 60 * 60 * 1000; // 1 hour before expiry
16
+ const MAX_MONCASH_AMOUNT = 75_000;
17
+
18
+ // ─── Errors ──────────────────────────────────────────────────────────────────
19
+
20
+ class BazikError extends Error {
21
+ /**
22
+ * @param {string} message
23
+ * @param {number} [status]
24
+ * @param {string} [code]
25
+ * @param {*} [details]
26
+ */
27
+ constructor(message, status, code, details) {
28
+ super(message);
29
+ this.name = "BazikError";
30
+ this.status = status ?? null;
31
+ this.code = code ?? null;
32
+ this.details = details ?? null;
33
+ }
34
+ }
35
+
36
+ class BazikAuthError extends BazikError {
37
+ constructor(message, status, code, details) {
38
+ super(message, status, code, details);
39
+ this.name = "BazikAuthError";
40
+ }
41
+ }
42
+
43
+ class BazikValidationError extends BazikError {
44
+ constructor(message, details) {
45
+ super(message, 400, "validation_error", details);
46
+ this.name = "BazikValidationError";
47
+ }
48
+ }
49
+
50
+ class BazikInsufficientFundsError extends BazikError {
51
+ constructor(message, details) {
52
+ super(message, 402, "insufficient_funds", details);
53
+ this.name = "BazikInsufficientFundsError";
54
+ }
55
+ }
56
+
57
+ class BazikRateLimitError extends BazikError {
58
+ constructor(message, details) {
59
+ super(message, 429, "rate_limit_exceeded", details);
60
+ this.name = "BazikRateLimitError";
61
+ }
62
+ }
63
+
64
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
65
+
66
+ /**
67
+ * Validate that a wallet number is 8 or 11 digits.
68
+ * @param {string} wallet
69
+ */
70
+ function validateWallet(wallet) {
71
+ if (typeof wallet !== "string" || !/^\d{8}(\d{3})?$/.test(wallet)) {
72
+ throw new BazikValidationError(
73
+ `Invalid wallet number "${wallet}". Must be 8 or 11 digits.`
74
+ );
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Validate a positive numeric amount.
80
+ * @param {number} amount
81
+ * @param {number} [max]
82
+ */
83
+ function validateAmount(amount, max) {
84
+ if (typeof amount !== "number" || !isFinite(amount) || amount <= 0) {
85
+ throw new BazikValidationError(
86
+ `Invalid amount: ${amount}. Must be a positive number.`
87
+ );
88
+ }
89
+ if (max !== undefined && amount > max) {
90
+ throw new BazikValidationError(
91
+ `Amount ${amount} exceeds maximum of ${max} HTG.`
92
+ );
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Validate that required fields are present in an object.
98
+ * @param {Record<string, *>} obj
99
+ * @param {string[]} fields
100
+ */
101
+ function validateRequired(obj, fields) {
102
+ const missing = fields.filter(
103
+ (f) => obj[f] === undefined || obj[f] === null || obj[f] === ""
104
+ );
105
+ if (missing.length > 0) {
106
+ throw new BazikValidationError(
107
+ `Missing required field(s): ${missing.join(", ")}`
108
+ );
109
+ }
110
+ }
111
+
112
+ // ─── HTTP Client (zero dependencies) ─────────────────────────────────────────
113
+
114
+ /**
115
+ * Minimal fetch wrapper.
116
+ * @param {string} url
117
+ * @param {RequestInit & { timeout?: number }} options
118
+ * @returns {Promise<{ status: number, data: * }>}
119
+ */
120
+ async function request(url, options = {}) {
121
+ const { timeout = 30_000, ...fetchOptions } = options;
122
+
123
+ const controller = new AbortController();
124
+ const timer = setTimeout(() => controller.abort(), timeout);
125
+
126
+ try {
127
+ const res = await fetch(url, {
128
+ ...fetchOptions,
129
+ signal: controller.signal,
130
+ });
131
+
132
+ let data;
133
+ const contentType = res.headers.get("content-type") || "";
134
+ if (contentType.includes("application/json")) {
135
+ data = await res.json();
136
+ } else {
137
+ data = await res.text();
138
+ }
139
+
140
+ return { status: res.status, data };
141
+ } finally {
142
+ clearTimeout(timer);
143
+ }
144
+ }
145
+
146
+ // ─── Bazik Client ────────────────────────────────────────────────────────────
147
+
148
+ class Bazik {
149
+ #userID;
150
+ #secretKey;
151
+ #baseURL;
152
+ #token;
153
+ #tokenExpiresAt;
154
+ #autoRefresh;
155
+ #timeout;
156
+ #onTokenRefresh;
157
+
158
+ /**
159
+ * Create a new Bazik client.
160
+ *
161
+ * @param {Object} config
162
+ * @param {string} config.userID — Your Bazik user ID (e.g. "bzk_c5b754a0_1757383229")
163
+ * @param {string} config.secretKey — Your secret key (e.g. "sk_...")
164
+ * @param {string} [config.baseURL] — API base URL (default: https://api.bazik.io)
165
+ * @param {boolean} [config.autoRefresh] — Automatically refresh token before expiry (default: true)
166
+ * @param {number} [config.timeout] — Request timeout in ms (default: 30000)
167
+ * @param {(token: string) => void} [config.onTokenRefresh] — Callback when token is refreshed
168
+ *
169
+ * @example
170
+ * // CommonJS
171
+ * const { Bazik } = require("bazik-sdk");
172
+ *
173
+ * // ESM
174
+ * import { Bazik } from "bazik-sdk";
175
+ *
176
+ * const bazik = new Bazik({
177
+ * userID: "bzk_c5b754a0_1757383229",
178
+ * secretKey: "sk_5b0ff521b331c73db55313dc82f17cab",
179
+ * });
180
+ */
181
+ constructor(config) {
182
+ if (!config || !config.userID || !config.secretKey) {
183
+ throw new BazikValidationError(
184
+ "Both userID and secretKey are required to initialize the Bazik client."
185
+ );
186
+ }
187
+
188
+ this.#userID = config.userID;
189
+ this.#secretKey = config.secretKey;
190
+ this.#baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/+$/, "");
191
+ this.#autoRefresh = config.autoRefresh !== false;
192
+ this.#timeout = config.timeout || 30_000;
193
+ this.#onTokenRefresh = config.onTokenRefresh || null;
194
+ this.#token = null;
195
+ this.#tokenExpiresAt = 0;
196
+
197
+ // Bind sub-modules
198
+ this.payments = new Payments(this);
199
+ this.transfers = new Transfers(this);
200
+ this.wallet = new Wallet(this);
201
+ }
202
+
203
+ // ── Token management ────────────────────────────────────────────────────
204
+
205
+ /**
206
+ * Authenticate and obtain an access token.
207
+ * The token is cached internally and reused for subsequent requests.
208
+ *
209
+ * @returns {Promise<{ success: boolean, token: string, user_id: string, expires_at: number, message: string }>}
210
+ *
211
+ * @example
212
+ * const auth = await bazik.authenticate();
213
+ * console.log("Token expires at:", new Date(auth.expires_at));
214
+ */
215
+ async authenticate() {
216
+ const { status, data } = await request(`${this.#baseURL}/token`, {
217
+ method: "POST",
218
+ headers: { "Content-Type": "application/json" },
219
+ timeout: this.#timeout,
220
+ body: JSON.stringify({
221
+ userID: this.#userID,
222
+ secretKey: this.#secretKey,
223
+ }),
224
+ });
225
+
226
+ if (status === 429) {
227
+ throw new BazikRateLimitError(
228
+ "Too many authentication attempts. Please wait before retrying.",
229
+ data
230
+ );
231
+ }
232
+
233
+ if (status === 401) {
234
+ throw new BazikAuthError(
235
+ data?.error?.message || "Invalid credentials.",
236
+ status,
237
+ data?.error?.code,
238
+ data?.error?.details
239
+ );
240
+ }
241
+
242
+ if (status !== 200 || !data?.token) {
243
+ throw new BazikError(
244
+ data?.error?.message || "Authentication failed.",
245
+ status,
246
+ data?.error?.code,
247
+ data
248
+ );
249
+ }
250
+
251
+ this.#token = data.token;
252
+ this.#tokenExpiresAt = data.expires_at;
253
+
254
+ if (this.#onTokenRefresh) {
255
+ this.#onTokenRefresh(data.token);
256
+ }
257
+
258
+ return data;
259
+ }
260
+
261
+ /**
262
+ * Returns true if the current token is still valid (with a safety margin).
263
+ * @returns {boolean}
264
+ */
265
+ isTokenValid() {
266
+ if (!this.#token) return false;
267
+ return Date.now() < this.#tokenExpiresAt - TOKEN_REFRESH_MARGIN_MS;
268
+ }
269
+
270
+ /**
271
+ * Get a valid token, refreshing if needed.
272
+ * @returns {Promise<string>}
273
+ */
274
+ async getToken() {
275
+ if (!this.#token || (this.#autoRefresh && !this.isTokenValid())) {
276
+ await this.authenticate();
277
+ }
278
+ return this.#token;
279
+ }
280
+
281
+ /**
282
+ * Internal: make an authenticated API request.
283
+ * @param {string} method
284
+ * @param {string} path
285
+ * @param {*} [body]
286
+ * @returns {Promise<*>}
287
+ */
288
+ async _request(method, path, body) {
289
+ const token = await this.getToken();
290
+
291
+ const headers = {
292
+ Authorization: `Bearer ${token}`,
293
+ "Content-Type": "application/json",
294
+ };
295
+
296
+ const opts = { method, headers, timeout: this.#timeout };
297
+ if (body !== undefined) {
298
+ opts.body = JSON.stringify(body);
299
+ }
300
+
301
+ const { status, data } = await request(
302
+ `${this.#baseURL}${path}`,
303
+ opts
304
+ );
305
+
306
+ // Handle common error statuses
307
+ if (status === 401) {
308
+ // Token may have expired — try one refresh
309
+ if (this.#autoRefresh) {
310
+ await this.authenticate();
311
+ const retryHeaders = {
312
+ Authorization: `Bearer ${this.#token}`,
313
+ "Content-Type": "application/json",
314
+ };
315
+ const retry = await request(`${this.#baseURL}${path}`, {
316
+ ...opts,
317
+ headers: retryHeaders,
318
+ });
319
+ if (retry.status === 401) {
320
+ throw new BazikAuthError(
321
+ "Authentication failed after token refresh.",
322
+ 401,
323
+ "unauthorized",
324
+ retry.data
325
+ );
326
+ }
327
+ return retry.data;
328
+ }
329
+ throw new BazikAuthError(
330
+ data?.error?.message || "Unauthorized.",
331
+ 401,
332
+ data?.error?.code,
333
+ data
334
+ );
335
+ }
336
+
337
+ if (status === 402) {
338
+ throw new BazikInsufficientFundsError(
339
+ data?.error?.message || "Insufficient funds.",
340
+ data
341
+ );
342
+ }
343
+
344
+ if (status === 429) {
345
+ throw new BazikRateLimitError(
346
+ data?.error?.message || "Rate limit exceeded.",
347
+ data
348
+ );
349
+ }
350
+
351
+ if (status >= 400) {
352
+ throw new BazikError(
353
+ data?.error?.message || data?.message || `Request failed with status ${status}`,
354
+ status,
355
+ data?.error?.code,
356
+ data
357
+ );
358
+ }
359
+
360
+ return data;
361
+ }
362
+ }
363
+
364
+ // ─── Payments sub-module ─────────────────────────────────────────────────────
365
+
366
+ class Payments {
367
+ #client;
368
+
369
+ constructor(client) {
370
+ this.#client = client;
371
+ }
372
+
373
+ /**
374
+ * Create a MonCash payment. The customer must be redirected to `redirectUrl`.
375
+ *
376
+ * @param {Object} params
377
+ * @param {number} params.gdes — Amount in Gourdes (max 75,000)
378
+ * @param {string} [params.successUrl] — Redirect URL on success
379
+ * @param {string} [params.errorUrl] — Redirect URL on error
380
+ * @param {string} [params.description] — Payment description
381
+ * @param {string} [params.referenceId] — Your reference ID
382
+ * @param {string} [params.customerFirstName]
383
+ * @param {string} [params.customerLastName]
384
+ * @param {string} [params.customerEmail]
385
+ * @param {string} [params.webhookUrl] — Webhook for status updates
386
+ * @param {Object} [params.metadata] — Arbitrary metadata
387
+ * @returns {Promise<Object>} — Contains orderId, redirectUrl, status, etc.
388
+ *
389
+ * @example
390
+ * const payment = await bazik.payments.create({
391
+ * gdes: 1284.00,
392
+ * successUrl: "https://mysite.com/success",
393
+ * errorUrl: "https://mysite.com/error",
394
+ * description: "iPhone Pro Max",
395
+ * referenceId: "ORDER-001",
396
+ * customerFirstName: "Franck",
397
+ * customerLastName: "Jean",
398
+ * customerEmail: "franck@example.com",
399
+ * webhookUrl: "https://mysite.com/webhook",
400
+ * });
401
+ *
402
+ * // Redirect the customer to complete the payment
403
+ * console.log("Redirect to:", payment.redirectUrl);
404
+ */
405
+ async create(params) {
406
+ validateRequired(params, ["gdes"]);
407
+ validateAmount(params.gdes, MAX_MONCASH_AMOUNT);
408
+
409
+ return this.#client._request("POST", "/moncash/token", params);
410
+ }
411
+
412
+ /**
413
+ * Verify a payment status by order ID.
414
+ *
415
+ * @param {string} orderId — The orderId returned by `create()`
416
+ * @returns {Promise<Object>} — Payment details with status, amount, metadata, etc.
417
+ *
418
+ * @example
419
+ * const status = await bazik.payments.verify("BZK_production_98630749_1760032902277_2ibu");
420
+ * if (status.status === "successful") {
421
+ * console.log("Payment confirmed!");
422
+ * }
423
+ */
424
+ async verify(orderId) {
425
+ if (!orderId) {
426
+ throw new BazikValidationError("orderId is required.");
427
+ }
428
+ return this.#client._request("GET", `/order/${encodeURIComponent(orderId)}`);
429
+ }
430
+
431
+ /**
432
+ * Poll payment status until it resolves or times out.
433
+ *
434
+ * @param {string} orderId
435
+ * @param {Object} [options]
436
+ * @param {number} [options.intervalMs=5000] — Polling interval
437
+ * @param {number} [options.timeoutMs=300000] — Max wait time (5 min)
438
+ * @returns {Promise<Object>} — Final payment status
439
+ *
440
+ * @example
441
+ * const result = await bazik.payments.waitForCompletion("BZK_...", {
442
+ * intervalMs: 5000,
443
+ * timeoutMs: 120000,
444
+ * });
445
+ */
446
+ async waitForCompletion(orderId, options = {}) {
447
+ const { intervalMs = 5000, timeoutMs = 300_000 } = options;
448
+ const deadline = Date.now() + timeoutMs;
449
+
450
+ while (Date.now() < deadline) {
451
+ const payment = await this.verify(orderId);
452
+ if (payment.status !== "pending") {
453
+ return payment;
454
+ }
455
+ await new Promise((r) => setTimeout(r, intervalMs));
456
+ }
457
+
458
+ throw new BazikError(
459
+ `Payment verification timed out after ${timeoutMs}ms.`,
460
+ null,
461
+ "timeout"
462
+ );
463
+ }
464
+
465
+ /**
466
+ * Send money to a MonCash wallet (withdraw / payout).
467
+ *
468
+ * @param {Object} params
469
+ * @param {number} params.gdes — Amount in HTG
470
+ * @param {string} params.wallet — Recipient phone (8 or 11 digits)
471
+ * @param {string} params.customerFirstName — Recipient first name
472
+ * @param {string} params.customerLastName — Recipient last name
473
+ * @param {string} [params.description]
474
+ * @param {string} [params.referenceId]
475
+ * @param {string} [params.customerEmail]
476
+ * @param {string} [params.webhookUrl]
477
+ * @returns {Promise<Object>}
478
+ *
479
+ * @example
480
+ * const withdrawal = await bazik.payments.withdraw({
481
+ * gdes: 500,
482
+ * wallet: "47556677",
483
+ * customerFirstName: "Melissa",
484
+ * customerLastName: "Francois",
485
+ * description: "Weekly earnings",
486
+ * });
487
+ */
488
+ async withdraw(params) {
489
+ validateRequired(params, [
490
+ "gdes",
491
+ "wallet",
492
+ "customerFirstName",
493
+ "customerLastName",
494
+ ]);
495
+ validateAmount(params.gdes);
496
+ validateWallet(params.wallet);
497
+
498
+ return this.#client._request("POST", "/moncash/withdraw", params);
499
+ }
500
+
501
+ /**
502
+ * Get account balance (MonCash).
503
+ *
504
+ * @returns {Promise<{ available: number, reserved: number, currency: string, environment: string, last_updated: string }>}
505
+ *
506
+ * @example
507
+ * const balance = await bazik.payments.getBalance();
508
+ * console.log(`Available: ${balance.available} ${balance.currency}`);
509
+ */
510
+ async getBalance() {
511
+ return this.#client._request("GET", "/balance");
512
+ }
513
+ }
514
+
515
+ // ─── Transfers sub-module ────────────────────────────────────────────────────
516
+
517
+ class Transfers {
518
+ #client;
519
+
520
+ constructor(client) {
521
+ this.#client = client;
522
+ }
523
+
524
+ /**
525
+ * Check MonCash customer/wallet status before sending a transfer.
526
+ *
527
+ * @param {string} wallet — MonCash phone number (8 digits)
528
+ * @returns {Promise<Object>} — Customer KYC level and status flags
529
+ *
530
+ * @example
531
+ * const status = await bazik.transfers.checkCustomer("37123456");
532
+ * console.log(status.customerStatus.type); // "fullkyc"
533
+ */
534
+ async checkCustomer(wallet) {
535
+ validateWallet(wallet);
536
+ return this.#client._request("POST", "/moncash/customers/status", {
537
+ wallet,
538
+ });
539
+ }
540
+
541
+ /**
542
+ * Create a MonCash transfer (send money to a wallet).
543
+ *
544
+ * @param {Object} params
545
+ * @param {number} params.gdes — Amount in HTG
546
+ * @param {string} params.wallet — Recipient phone (8 digits)
547
+ * @param {string} params.customerFirstName
548
+ * @param {string} params.customerLastName
549
+ * @param {string} [params.description]
550
+ * @param {string} [params.referenceId]
551
+ * @param {string} [params.customerEmail]
552
+ * @param {string} [params.webhookUrl]
553
+ * @returns {Promise<Object>}
554
+ *
555
+ * @example
556
+ * const transfer = await bazik.transfers.moncash({
557
+ * gdes: 500,
558
+ * wallet: "47556677",
559
+ * customerFirstName: "Melissa",
560
+ * customerLastName: "Francois",
561
+ * description: "Pou ou peye lekol la",
562
+ * });
563
+ * console.log(transfer.transaction_id); // "TRF_..."
564
+ */
565
+ async moncash(params) {
566
+ validateRequired(params, [
567
+ "gdes",
568
+ "wallet",
569
+ "customerFirstName",
570
+ "customerLastName",
571
+ ]);
572
+ validateAmount(params.gdes);
573
+ validateWallet(params.wallet);
574
+
575
+ return this.#client._request("POST", "/moncash/transfers", params);
576
+ }
577
+
578
+ /**
579
+ * Create a NatCash transfer.
580
+ *
581
+ * @param {Object} params
582
+ * @param {number} params.gdes — Amount in HTG
583
+ * @param {string} params.wallet — Recipient phone (8 digits)
584
+ * @param {string} params.customerFirstName
585
+ * @param {string} params.customerLastName
586
+ * @param {string} [params.description]
587
+ * @param {string} [params.referenceId]
588
+ * @param {string} [params.customerEmail]
589
+ * @param {string} [params.webhookUrl]
590
+ * @returns {Promise<Object>}
591
+ *
592
+ * @example
593
+ * const transfer = await bazik.transfers.natcash({
594
+ * gdes: 50,
595
+ * wallet: "44556677",
596
+ * customerFirstName: "Marie",
597
+ * customerLastName: "Pierre",
598
+ * });
599
+ */
600
+ async natcash(params) {
601
+ validateRequired(params, [
602
+ "gdes",
603
+ "wallet",
604
+ "customerFirstName",
605
+ "customerLastName",
606
+ ]);
607
+ validateAmount(params.gdes);
608
+ validateWallet(params.wallet);
609
+
610
+ return this.#client._request("POST", "/natcash/transfers", params);
611
+ }
612
+
613
+ /**
614
+ * Get transfer status by transaction ID.
615
+ *
616
+ * @param {string} transactionId — e.g. "TRF_1761961466_eafd0ac3"
617
+ * @returns {Promise<Object>}
618
+ *
619
+ * @example
620
+ * const status = await bazik.transfers.getStatus("TRF_1761961466_eafd0ac3");
621
+ * if (status.status === "successful") {
622
+ * console.log("Transfer completed!");
623
+ * }
624
+ */
625
+ async getStatus(transactionId) {
626
+ if (!transactionId) {
627
+ throw new BazikValidationError("transactionId is required.");
628
+ }
629
+ return this.#client._request(
630
+ "GET",
631
+ `/transfers/${encodeURIComponent(transactionId)}`
632
+ );
633
+ }
634
+
635
+ /**
636
+ * Get a fee quote before creating a transfer.
637
+ *
638
+ * @param {number} amount — Delivery amount in HTG
639
+ * @param {"moncash"|"natcash"} provider
640
+ * @returns {Promise<{ delivery_amount: number, fee: number, total_cost: number, currency: string, provider: string, fee_percentage: number }>}
641
+ *
642
+ * @example
643
+ * const quote = await bazik.transfers.getQuote(1000, "moncash");
644
+ * console.log(`Fee: ${quote.fee} HTG | Total: ${quote.total_cost} HTG`);
645
+ */
646
+ async getQuote(amount, provider) {
647
+ validateAmount(amount);
648
+ if (!["moncash", "natcash"].includes(provider)) {
649
+ throw new BazikValidationError(
650
+ `Invalid provider "${provider}". Must be "moncash" or "natcash".`
651
+ );
652
+ }
653
+ return this.#client._request("POST", "/transfers/quote", {
654
+ amount,
655
+ provider,
656
+ });
657
+ }
658
+ }
659
+
660
+ // ─── Wallet sub-module ───────────────────────────────────────────────────────
661
+
662
+ class Wallet {
663
+ #client;
664
+
665
+ constructor(client) {
666
+ this.#client = client;
667
+ }
668
+
669
+ /**
670
+ * Get wallet balance.
671
+ *
672
+ * @returns {Promise<{ available: number, reserved: number, currency: string, environment: string, last_updated: string }>}
673
+ *
674
+ * @example
675
+ * const wallet = await bazik.wallet.getBalance();
676
+ * console.log(`Available: ${wallet.available} HTG`);
677
+ */
678
+ async getBalance() {
679
+ return this.#client._request("GET", "/wallet");
680
+ }
681
+ }
682
+
683
+ // ─── Exports ─────────────────────────────────────────────────────────────────
684
+
685
+ module.exports = {
686
+ Bazik,
687
+ BazikError,
688
+ BazikAuthError,
689
+ BazikValidationError,
690
+ BazikInsufficientFundsError,
691
+ BazikRateLimitError,
692
+ };
package/src/index.mjs ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Bazik SDK — ESM entry point
3
+ * Re-exports all public APIs from the CommonJS source.
4
+ */
5
+ import pkg from "./index.js";
6
+
7
+ export const {
8
+ Bazik,
9
+ BazikError,
10
+ BazikAuthError,
11
+ BazikValidationError,
12
+ BazikInsufficientFundsError,
13
+ BazikRateLimitError,
14
+ } = pkg;
15
+
16
+ export default Bazik;