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 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,5 @@
1
+ export class AccountsResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/accounts', { query }); }
4
+ get(id) { return this.client.get(`/accounts/${id}`); }
5
+ }
@@ -0,0 +1,4 @@
1
+ export class AuditResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/audit', { query }); }
4
+ }
@@ -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,4 @@
1
+ export class DividendsResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/dividends', { query }); }
4
+ }
@@ -0,0 +1,4 @@
1
+ export class DocumentRemindersResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/document_reminders', { query }); }
4
+ }
@@ -0,0 +1,4 @@
1
+ export class DocumentsResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/documents', { query }); }
4
+ }
@@ -0,0 +1,4 @@
1
+ export class ElectionNoticesResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/election_notices', { query }); }
4
+ }
@@ -0,0 +1,5 @@
1
+ export class FinesResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/fines', { query }); }
4
+ create(body) { return this.client.post('/fines', { body }); }
5
+ }
@@ -0,0 +1,5 @@
1
+ export class LiabilitiesResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/liabilities', { query }); }
4
+ create(body) { return this.client.post('/liabilities', { body }); }
5
+ }
@@ -0,0 +1,6 @@
1
+ export class LoansResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/loans', { query }); }
4
+ get(id) { return this.client.get(`/loans/${id}`); }
5
+ create(body) { return this.client.post('/loans', { body }); }
6
+ }
@@ -0,0 +1,5 @@
1
+ export class MeetingsResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/meetings', { query }); }
4
+ create(body) { return this.client.post('/meetings', { body }); }
5
+ }
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ export class ProjectsResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/projects', { query }); }
4
+ create(body) { return this.client.post('/projects', { body }); }
5
+ }
@@ -0,0 +1,4 @@
1
+ export class ReportsResource {
2
+ constructor(client) { this.client = client; }
3
+ get(query) { return this.client.get('/reports', { query }); }
4
+ }
@@ -0,0 +1,4 @@
1
+ export class WelfareResource {
2
+ constructor(client) { this.client = client; }
3
+ list(query) { return this.client.get('/welfare', { query }); }
4
+ }