chamapay 1.0.0
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 +77 -0
- package/package.json +24 -0
- package/src/client.js +84 -0
- package/src/errors.js +45 -0
- package/src/index.js +41 -0
- package/src/resources/accounts.js +5 -0
- package/src/resources/audit.js +4 -0
- package/src/resources/contributions.js +9 -0
- package/src/resources/dividends.js +4 -0
- package/src/resources/document_reminders.js +4 -0
- package/src/resources/documents.js +4 -0
- package/src/resources/election_notices.js +4 -0
- package/src/resources/fines.js +5 -0
- package/src/resources/liabilities.js +5 -0
- package/src/resources/loans.js +6 -0
- package/src/resources/meetings.js +5 -0
- package/src/resources/members.js +8 -0
- package/src/resources/projects.js +5 -0
- package/src/resources/reports.js +4 -0
- package/src/resources/welfare.js +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ChamaPay JavaScript SDK
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for the ChamaPay API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install chamapay
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import ChamaPay from 'chamapay';
|
|
15
|
+
|
|
16
|
+
const api = new ChamaPay('cpk_live_your_api_key', 'your_api_secret');
|
|
17
|
+
|
|
18
|
+
// List members
|
|
19
|
+
const members = await api.members.list({ status: 'Active', page: 1, limit: 20 });
|
|
20
|
+
|
|
21
|
+
// Create a contribution (idempotent)
|
|
22
|
+
const contribution = await api.contributions.create(
|
|
23
|
+
{ member_id: 'MFT001', amount: 5000 },
|
|
24
|
+
'550e8400-e29b-41d4-a716-446655440000' // Idempotency-Key
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Get loan details
|
|
28
|
+
const loan = await api.loans.get('LN001');
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API Reference
|
|
32
|
+
|
|
33
|
+
Every resource method returns a Promise resolving to the API JSON response.
|
|
34
|
+
|
|
35
|
+
| Resource | Methods |
|
|
36
|
+
|-------------|--------------------------------------------|
|
|
37
|
+
| members | list, get, create, update, delete |
|
|
38
|
+
| contributions | list, get, create (with idempotency key) |
|
|
39
|
+
| loans | list, get, create |
|
|
40
|
+
| fines | list, create |
|
|
41
|
+
| meetings | list, create |
|
|
42
|
+
| projects | list, create |
|
|
43
|
+
| accounts | list, get |
|
|
44
|
+
| welfare | list |
|
|
45
|
+
| dividends | list |
|
|
46
|
+
| documents | list |
|
|
47
|
+
| electionNotices | list |
|
|
48
|
+
| documentReminders | list |
|
|
49
|
+
| audit | list |
|
|
50
|
+
| liabilities | list, create |
|
|
51
|
+
| reports | get |
|
|
52
|
+
|
|
53
|
+
## Error Handling
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
import { ChamaPayError, RateLimitError, ValidationError } from 'chamapay';
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await api.members.create({ /* ... */ });
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (err instanceof ValidationError) {
|
|
62
|
+
console.error('Validation failed:', err.details);
|
|
63
|
+
} else if (err instanceof RateLimitError) {
|
|
64
|
+
console.error(`Rate limited. Retry after ${err.retryAfter}s`);
|
|
65
|
+
} else if (err instanceof ChamaPayError) {
|
|
66
|
+
console.error(`API Error [${err.code}]:`, err.message);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Publishing
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm publish
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Requires an npm account with access to the `chamapay` package.
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chamapay",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Node.js SDK for ChamaPay API — Financial Infrastructure for African Savings Groups",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test src/**/*.test.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"chamapay",
|
|
14
|
+
"sacco",
|
|
15
|
+
"savings",
|
|
16
|
+
"mpesa",
|
|
17
|
+
"africa",
|
|
18
|
+
"fintech"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ChamaPayError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError } from './errors.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = 'https://api.chamapay.co.ke/v1';
|
|
4
|
+
const DEFAULT_TIMEOUT = 30000;
|
|
5
|
+
|
|
6
|
+
export class ChamaPayClient {
|
|
7
|
+
constructor(apiKey, apiSecret, opts = {}) {
|
|
8
|
+
if (!apiKey || !apiSecret) {
|
|
9
|
+
throw new ChamaPayError('API key and secret are required');
|
|
10
|
+
}
|
|
11
|
+
this.apiKey = apiKey;
|
|
12
|
+
this.apiSecret = apiSecret;
|
|
13
|
+
this.baseUrl = opts.baseUrl || BASE_URL;
|
|
14
|
+
this.timeout = opts.timeout || DEFAULT_TIMEOUT;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_token() {
|
|
18
|
+
return `${this.apiKey}:${this.apiSecret}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_headers() {
|
|
22
|
+
return {
|
|
23
|
+
'Authorization': `Bearer ${this._token()}`,
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'User-Agent': 'chamapay-node/1.0.0',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async _request(method, path, opts = {}) {
|
|
30
|
+
const url = new URL(path, this.baseUrl);
|
|
31
|
+
if (opts.query) {
|
|
32
|
+
Object.entries(opts.query).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(url, {
|
|
40
|
+
method,
|
|
41
|
+
headers: { ...this._headers(), ...opts.headers },
|
|
42
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
43
|
+
signal: controller.signal,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let data;
|
|
47
|
+
const ct = res.headers.get('content-type') || '';
|
|
48
|
+
if (ct.includes('application/json')) {
|
|
49
|
+
data = await res.json();
|
|
50
|
+
} else {
|
|
51
|
+
data = { error: { message: await res.text() } };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
const err = data.error || data;
|
|
56
|
+
const msg = err.message || `Request failed with status ${res.status}`;
|
|
57
|
+
switch (res.status) {
|
|
58
|
+
case 401: throw new AuthenticationError(msg, err);
|
|
59
|
+
case 404: throw new NotFoundError(msg, err);
|
|
60
|
+
case 422: throw new ValidationError(msg, err);
|
|
61
|
+
case 429: throw new RateLimitError(msg, parseInt(res.headers.get('retry-after') || '30'), err);
|
|
62
|
+
case 500: throw new ServerError(msg, err);
|
|
63
|
+
default: throw new ChamaPayError(msg, res.status, err.code, err);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return data;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (err.name === 'AbortError') {
|
|
70
|
+
throw new ChamaPayError('Request timed out', 0, 'timeout');
|
|
71
|
+
}
|
|
72
|
+
if (err instanceof ChamaPayError) throw err;
|
|
73
|
+
throw new ChamaPayError(err.message, 0, 'connection_error');
|
|
74
|
+
} finally {
|
|
75
|
+
clearTimeout(timeoutId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get(path, opts) { return this._request('GET', path, opts); }
|
|
80
|
+
post(path, opts) { return this._request('POST', path, opts); }
|
|
81
|
+
put(path, opts) { return this._request('PUT', path, opts); }
|
|
82
|
+
patch(path, opts) { return this._request('PATCH', path, opts); }
|
|
83
|
+
delete(path, opts) { return this._request('DELETE', path, opts); }
|
|
84
|
+
}
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class ChamaPayError extends Error {
|
|
2
|
+
constructor(message, status, code, details) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'ChamaPayError';
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.details = details;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class AuthenticationError extends ChamaPayError {
|
|
12
|
+
constructor(message, details) {
|
|
13
|
+
super(message, 401, 'authentication_error', details);
|
|
14
|
+
this.name = 'AuthenticationError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class RateLimitError extends ChamaPayError {
|
|
19
|
+
constructor(message, retryAfter, details) {
|
|
20
|
+
super(message, 429, 'rate_limit_error', details);
|
|
21
|
+
this.name = 'RateLimitError';
|
|
22
|
+
this.retryAfter = retryAfter;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ValidationError extends ChamaPayError {
|
|
27
|
+
constructor(message, details) {
|
|
28
|
+
super(message, 422, 'validation_error', details);
|
|
29
|
+
this.name = 'ValidationError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class NotFoundError extends ChamaPayError {
|
|
34
|
+
constructor(message, details) {
|
|
35
|
+
super(message, 404, 'not_found', details);
|
|
36
|
+
this.name = 'NotFoundError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class ServerError extends ChamaPayError {
|
|
41
|
+
constructor(message, details) {
|
|
42
|
+
super(message, 500, 'server_error', details);
|
|
43
|
+
this.name = 'ServerError';
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ChamaPayClient } from './client.js';
|
|
2
|
+
import { MembersResource } from './resources/members.js';
|
|
3
|
+
import { ContributionsResource } from './resources/contributions.js';
|
|
4
|
+
import { LoansResource } from './resources/loans.js';
|
|
5
|
+
import { FinesResource } from './resources/fines.js';
|
|
6
|
+
import { MeetingsResource } from './resources/meetings.js';
|
|
7
|
+
import { ProjectsResource } from './resources/projects.js';
|
|
8
|
+
import { AccountsResource } from './resources/accounts.js';
|
|
9
|
+
import { WelfareResource } from './resources/welfare.js';
|
|
10
|
+
import { DividendsResource } from './resources/dividends.js';
|
|
11
|
+
import { DocumentsResource } from './resources/documents.js';
|
|
12
|
+
import { ElectionNoticesResource } from './resources/election_notices.js';
|
|
13
|
+
import { DocumentRemindersResource } from './resources/document_reminders.js';
|
|
14
|
+
import { AuditResource } from './resources/audit.js';
|
|
15
|
+
import { LiabilitiesResource } from './resources/liabilities.js';
|
|
16
|
+
import { ReportsResource } from './resources/reports.js';
|
|
17
|
+
import { ChamaPayError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError } from './errors.js';
|
|
18
|
+
|
|
19
|
+
export class ChamaPay {
|
|
20
|
+
constructor(apiKey, apiSecret, opts) {
|
|
21
|
+
this._client = new ChamaPayClient(apiKey, apiSecret, opts);
|
|
22
|
+
this.members = new MembersResource(this._client);
|
|
23
|
+
this.contributions = new ContributionsResource(this._client);
|
|
24
|
+
this.loans = new LoansResource(this._client);
|
|
25
|
+
this.fines = new FinesResource(this._client);
|
|
26
|
+
this.meetings = new MeetingsResource(this._client);
|
|
27
|
+
this.projects = new ProjectsResource(this._client);
|
|
28
|
+
this.accounts = new AccountsResource(this._client);
|
|
29
|
+
this.welfare = new WelfareResource(this._client);
|
|
30
|
+
this.dividends = new DividendsResource(this._client);
|
|
31
|
+
this.documents = new DocumentsResource(this._client);
|
|
32
|
+
this.electionNotices = new ElectionNoticesResource(this._client);
|
|
33
|
+
this.documentReminders = new DocumentRemindersResource(this._client);
|
|
34
|
+
this.audit = new AuditResource(this._client);
|
|
35
|
+
this.liabilities = new LiabilitiesResource(this._client);
|
|
36
|
+
this.reports = new ReportsResource(this._client);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { ChamaPayError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError };
|
|
41
|
+
export default ChamaPay;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class ContributionsResource {
|
|
2
|
+
constructor(client) { this.client = client; }
|
|
3
|
+
list(query) { return this.client.get('/contributions', { query }); }
|
|
4
|
+
get(id) { return this.client.get(`/contributions/${id}`); }
|
|
5
|
+
create(body, idempotencyKey) {
|
|
6
|
+
const headers = idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {};
|
|
7
|
+
return this.client.post('/contributions', { body, headers });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export class MembersResource {
|
|
2
|
+
constructor(client) { this.client = client; }
|
|
3
|
+
list(query) { return this.client.get('/members', { query }); }
|
|
4
|
+
get(id) { return this.client.get(`/members/${id}`); }
|
|
5
|
+
create(body) { return this.client.post('/members', { body }); }
|
|
6
|
+
update(id, body) { return this.client.put(`/members/${id}`, { body }); }
|
|
7
|
+
delete(id) { return this.client.delete(`/members/${id}`); }
|
|
8
|
+
}
|