@jazadev/node 0.1.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 +59 -0
- package/dist/index.cjs +226 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +116 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +196 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# `@jazadev/node`
|
|
2
|
+
|
|
3
|
+
Official Node.js backend SDK for [Jaza](https://jaza.dev) prepaid / metered billing.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @jazadev/node
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Jaza } from '@jazadev/node';
|
|
15
|
+
|
|
16
|
+
const jaza = new Jaza({
|
|
17
|
+
secretKey: process.env.JAZA_SECRET_KEY!, // jz_test_sk_…
|
|
18
|
+
publicKey: process.env.JAZA_PUBLIC_KEY!, // jz_test_pk_…
|
|
19
|
+
// apiBaseUrl: 'http://localhost:3001', // local jaza-api
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// 1. Create a customer once; store customer.id in your DB
|
|
23
|
+
const customer = await jaza.createCustomer({
|
|
24
|
+
name: 'Amina Okello',
|
|
25
|
+
email: 'amina@example.com', // and/or phoneNumber
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// 2. Issue a top-up JWT for your frontend SDK
|
|
29
|
+
const session = await jaza.topUp({ customerId: customer.id });
|
|
30
|
+
// Pass session.token + jaza.publicKey to the client.
|
|
31
|
+
// Frontend: X-Jaza-Public-Key + Authorization: Bearer <token>
|
|
32
|
+
// → GET /v1/public/bundles (PawaPay deposits come later)
|
|
33
|
+
|
|
34
|
+
// 3. Meter usage
|
|
35
|
+
await jaza.consume({
|
|
36
|
+
customerId: customer.id,
|
|
37
|
+
featureCode: 'SEND_MESSAGE',
|
|
38
|
+
idempotencyKey: `msg_${Date.now()}`,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 4. Poll top-up session status
|
|
42
|
+
const status = await jaza.check({ topUpId: session.id });
|
|
43
|
+
console.log(status.status); // PENDING until deposits complete
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API surface
|
|
47
|
+
|
|
48
|
+
| Method | Description |
|
|
49
|
+
|--------|-------------|
|
|
50
|
+
| `createCustomer({ name, email?, phoneNumber? })` | Returns `cus_…` |
|
|
51
|
+
| `topUp({ customerId })` | Returns session + JWT `token` |
|
|
52
|
+
| `consume({ customerId, featureCode \| credits, idempotencyKey })` | Debit wallet |
|
|
53
|
+
| `check({ topUpId })` | Session status |
|
|
54
|
+
|
|
55
|
+
Errors throw `JazaError` with `statusCode`, `code`, and `raw`.
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_API_BASE_URL: () => DEFAULT_API_BASE_URL,
|
|
24
|
+
Jaza: () => Jaza,
|
|
25
|
+
JazaError: () => JazaError,
|
|
26
|
+
VERSION: () => VERSION
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/errors.ts
|
|
31
|
+
var JazaError = class _JazaError extends Error {
|
|
32
|
+
statusCode;
|
|
33
|
+
code;
|
|
34
|
+
raw;
|
|
35
|
+
constructor(message, options) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "JazaError";
|
|
38
|
+
this.statusCode = options.statusCode;
|
|
39
|
+
this.code = options.code ?? "api_error";
|
|
40
|
+
this.raw = options.raw ?? null;
|
|
41
|
+
}
|
|
42
|
+
static fromResponse(statusCode, body) {
|
|
43
|
+
const parsed = body ?? {};
|
|
44
|
+
const message = typeof parsed.message === "string" ? parsed.message : `Jaza API request failed with status ${statusCode}`;
|
|
45
|
+
const code = typeof parsed.error === "string" ? parsed.error : statusCode === 401 ? "unauthorized" : statusCode === 404 ? "not_found" : statusCode === 409 ? "conflict" : statusCode === 400 ? "bad_request" : "api_error";
|
|
46
|
+
return new _JazaError(message, { statusCode, code, raw: body });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/version.ts
|
|
51
|
+
var VERSION = "0.1.0";
|
|
52
|
+
var DEFAULT_API_BASE_URL = "https://api.jaza.dev";
|
|
53
|
+
|
|
54
|
+
// src/client.ts
|
|
55
|
+
var HttpClient = class {
|
|
56
|
+
constructor(apiBaseUrl, secretKey) {
|
|
57
|
+
this.apiBaseUrl = apiBaseUrl;
|
|
58
|
+
this.secretKey = secretKey;
|
|
59
|
+
}
|
|
60
|
+
apiBaseUrl;
|
|
61
|
+
secretKey;
|
|
62
|
+
async request(method, path, body) {
|
|
63
|
+
const url = `${this.apiBaseUrl.replace(/\/$/, "")}${path}`;
|
|
64
|
+
const headers = {
|
|
65
|
+
Authorization: `Bearer ${this.secretKey}`,
|
|
66
|
+
Accept: "application/json",
|
|
67
|
+
"User-Agent": `@jazadev/node/${VERSION}`
|
|
68
|
+
};
|
|
69
|
+
if (body !== void 0) {
|
|
70
|
+
headers["Content-Type"] = "application/json";
|
|
71
|
+
}
|
|
72
|
+
const response = await fetch(url, {
|
|
73
|
+
method,
|
|
74
|
+
headers,
|
|
75
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
76
|
+
});
|
|
77
|
+
const text = await response.text();
|
|
78
|
+
let parsed = null;
|
|
79
|
+
if (text) {
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(text);
|
|
82
|
+
} catch {
|
|
83
|
+
parsed = { message: text };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw JazaError.fromResponse(response.status, parsed);
|
|
88
|
+
}
|
|
89
|
+
return parsed;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/jaza.ts
|
|
94
|
+
function assertConfig(config) {
|
|
95
|
+
if (!config?.secretKey || typeof config.secretKey !== "string") {
|
|
96
|
+
throw new JazaError("secretKey is required", {
|
|
97
|
+
statusCode: 0,
|
|
98
|
+
code: "invalid_config"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (!config.secretKey.includes("_sk_")) {
|
|
102
|
+
throw new JazaError("secretKey must be a merchant secret key (jz_*_sk_*)", {
|
|
103
|
+
statusCode: 0,
|
|
104
|
+
code: "invalid_config"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (!config?.publicKey || typeof config.publicKey !== "string") {
|
|
108
|
+
throw new JazaError("publicKey is required", {
|
|
109
|
+
statusCode: 0,
|
|
110
|
+
code: "invalid_config"
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (!config.publicKey.includes("_pk_")) {
|
|
114
|
+
throw new JazaError("publicKey must be a merchant public key (jz_*_pk_*)", {
|
|
115
|
+
statusCode: 0,
|
|
116
|
+
code: "invalid_config"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function assertCreateCustomer(params) {
|
|
121
|
+
if (!params?.name?.trim()) {
|
|
122
|
+
throw new JazaError("name is required", {
|
|
123
|
+
statusCode: 0,
|
|
124
|
+
code: "invalid_request"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (!params.email && !params.phoneNumber) {
|
|
128
|
+
throw new JazaError("email or phoneNumber is required", {
|
|
129
|
+
statusCode: 0,
|
|
130
|
+
code: "invalid_request"
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function assertConsume(params) {
|
|
135
|
+
if (!params?.customerId?.trim()) {
|
|
136
|
+
throw new JazaError("customerId is required", {
|
|
137
|
+
statusCode: 0,
|
|
138
|
+
code: "invalid_request"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (!params.idempotencyKey?.trim()) {
|
|
142
|
+
throw new JazaError("idempotencyKey is required", {
|
|
143
|
+
statusCode: 0,
|
|
144
|
+
code: "invalid_request"
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const hasFeature = Boolean(params.featureCode);
|
|
148
|
+
const hasCredits = params.credits !== void 0;
|
|
149
|
+
if (!hasFeature && !hasCredits) {
|
|
150
|
+
throw new JazaError("featureCode or credits is required", {
|
|
151
|
+
statusCode: 0,
|
|
152
|
+
code: "invalid_request"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (hasFeature && hasCredits) {
|
|
156
|
+
throw new JazaError("Provide featureCode or credits, not both", {
|
|
157
|
+
statusCode: 0,
|
|
158
|
+
code: "invalid_request"
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
var Jaza = class {
|
|
163
|
+
publicKey;
|
|
164
|
+
http;
|
|
165
|
+
constructor(config) {
|
|
166
|
+
assertConfig(config);
|
|
167
|
+
this.publicKey = config.publicKey;
|
|
168
|
+
this.http = new HttpClient(
|
|
169
|
+
config.apiBaseUrl ?? DEFAULT_API_BASE_URL,
|
|
170
|
+
config.secretKey
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
/** Create a Stripe-like customer (`cus_…`) and zero-balance wallet. */
|
|
174
|
+
createCustomer(params) {
|
|
175
|
+
assertCreateCustomer(params);
|
|
176
|
+
return this.http.request("POST", "/v1/customers", {
|
|
177
|
+
name: params.name,
|
|
178
|
+
email: params.email,
|
|
179
|
+
phoneNumber: params.phoneNumber
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/** Issue a top-up session JWT for the frontend (bundles / future deposits). */
|
|
183
|
+
topUp(params) {
|
|
184
|
+
if (!params?.customerId?.trim()) {
|
|
185
|
+
throw new JazaError("customerId is required", {
|
|
186
|
+
statusCode: 0,
|
|
187
|
+
code: "invalid_request"
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return this.http.request("POST", "/v1/top-ups", {
|
|
191
|
+
customerId: params.customerId
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Debit credits for a customer (feature code or raw credits). */
|
|
195
|
+
consume(params) {
|
|
196
|
+
assertConsume(params);
|
|
197
|
+
return this.http.request("POST", "/v1/credits/consume", {
|
|
198
|
+
customerId: params.customerId,
|
|
199
|
+
idempotencyKey: params.idempotencyKey,
|
|
200
|
+
featureCode: params.featureCode,
|
|
201
|
+
credits: params.credits,
|
|
202
|
+
reason: params.reason
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/** Check whether a top-up session completed (PENDING until PawaPay deposits land). */
|
|
206
|
+
check(params) {
|
|
207
|
+
if (!params?.topUpId?.trim()) {
|
|
208
|
+
throw new JazaError("topUpId is required", {
|
|
209
|
+
statusCode: 0,
|
|
210
|
+
code: "invalid_request"
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return this.http.request(
|
|
214
|
+
"GET",
|
|
215
|
+
`/v1/top-ups/${encodeURIComponent(params.topUpId)}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
220
|
+
0 && (module.exports = {
|
|
221
|
+
DEFAULT_API_BASE_URL,
|
|
222
|
+
Jaza,
|
|
223
|
+
JazaError,
|
|
224
|
+
VERSION
|
|
225
|
+
});
|
|
226
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/version.ts","../src/client.ts","../src/jaza.ts"],"sourcesContent":["export { Jaza } from './jaza.js';\nexport { JazaError } from './errors.js';\nexport { DEFAULT_API_BASE_URL, VERSION } from './version.js';\nexport type {\n ConsumeParams,\n ConsumeResult,\n CreateCustomerParams,\n Customer,\n JazaConfig,\n JazaErrorBody,\n LedgerEntry,\n TopUpSession,\n TopUpSessionStatus,\n Wallet,\n} from './types.js';\n","import type { JazaErrorBody } from './types.js';\n\nexport class JazaError extends Error {\n readonly statusCode: number;\n readonly code: string;\n readonly raw: unknown;\n\n constructor(message: string, options: {\n statusCode: number;\n code?: string;\n raw?: unknown;\n }) {\n super(message);\n this.name = 'JazaError';\n this.statusCode = options.statusCode;\n this.code = options.code ?? 'api_error';\n this.raw = options.raw ?? null;\n }\n\n static fromResponse(statusCode: number, body: unknown): JazaError {\n const parsed = (body ?? {}) as JazaErrorBody;\n const message =\n typeof parsed.message === 'string'\n ? parsed.message\n : `Jaza API request failed with status ${statusCode}`;\n const code =\n typeof parsed.error === 'string'\n ? parsed.error\n : statusCode === 401\n ? 'unauthorized'\n : statusCode === 404\n ? 'not_found'\n : statusCode === 409\n ? 'conflict'\n : statusCode === 400\n ? 'bad_request'\n : 'api_error';\n return new JazaError(message, { statusCode, code, raw: body });\n }\n}\n","export const VERSION = '0.1.0';\n\nexport const DEFAULT_API_BASE_URL = 'https://api.jaza.dev';\n","import { JazaError } from './errors.js';\nimport { VERSION } from './version.js';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\nexport class HttpClient {\n constructor(\n private readonly apiBaseUrl: string,\n private readonly secretKey: string,\n ) {}\n\n async request<T>(\n method: HttpMethod,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${this.apiBaseUrl.replace(/\\/$/, '')}${path}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.secretKey}`,\n Accept: 'application/json',\n 'User-Agent': `@jazadev/node/${VERSION}`,\n };\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n const text = await response.text();\n let parsed: unknown = null;\n if (text) {\n try {\n parsed = JSON.parse(text) as unknown;\n } catch {\n parsed = { message: text };\n }\n }\n\n if (!response.ok) {\n throw JazaError.fromResponse(response.status, parsed);\n }\n\n return parsed as T;\n }\n}\n","import { HttpClient } from './client.js';\nimport { JazaError } from './errors.js';\nimport type {\n ConsumeParams,\n ConsumeResult,\n CreateCustomerParams,\n Customer,\n JazaConfig,\n TopUpSession,\n} from './types.js';\nimport { DEFAULT_API_BASE_URL } from './version.js';\n\nfunction assertConfig(config: JazaConfig): void {\n if (!config?.secretKey || typeof config.secretKey !== 'string') {\n throw new JazaError('secretKey is required', {\n statusCode: 0,\n code: 'invalid_config',\n });\n }\n if (!config.secretKey.includes('_sk_')) {\n throw new JazaError('secretKey must be a merchant secret key (jz_*_sk_*)', {\n statusCode: 0,\n code: 'invalid_config',\n });\n }\n if (!config?.publicKey || typeof config.publicKey !== 'string') {\n throw new JazaError('publicKey is required', {\n statusCode: 0,\n code: 'invalid_config',\n });\n }\n if (!config.publicKey.includes('_pk_')) {\n throw new JazaError('publicKey must be a merchant public key (jz_*_pk_*)', {\n statusCode: 0,\n code: 'invalid_config',\n });\n }\n}\n\nfunction assertCreateCustomer(params: CreateCustomerParams): void {\n if (!params?.name?.trim()) {\n throw new JazaError('name is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n if (!params.email && !params.phoneNumber) {\n throw new JazaError('email or phoneNumber is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n}\n\nfunction assertConsume(params: ConsumeParams): void {\n if (!params?.customerId?.trim()) {\n throw new JazaError('customerId is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n if (!params.idempotencyKey?.trim()) {\n throw new JazaError('idempotencyKey is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n const hasFeature = Boolean(params.featureCode);\n const hasCredits = params.credits !== undefined;\n if (!hasFeature && !hasCredits) {\n throw new JazaError('featureCode or credits is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n if (hasFeature && hasCredits) {\n throw new JazaError('Provide featureCode or credits, not both', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n}\n\n/**\n * Official Node.js client for Jaza.\n *\n * @example\n * ```ts\n * const jaza = new Jaza({ secretKey, publicKey });\n * const customer = await jaza.createCustomer({ name: 'Amina', email: 'a@x.com' });\n * const session = await jaza.topUp({ customerId: customer.id });\n * // hand session.token + publicKey to your frontend SDK\n * await jaza.consume({ customerId: customer.id, featureCode: 'SEND_MESSAGE', idempotencyKey: '...' });\n * await jaza.check({ topUpId: session.id });\n * ```\n */\nexport class Jaza {\n readonly publicKey: string;\n private readonly http: HttpClient;\n\n constructor(config: JazaConfig) {\n assertConfig(config);\n this.publicKey = config.publicKey;\n this.http = new HttpClient(\n config.apiBaseUrl ?? DEFAULT_API_BASE_URL,\n config.secretKey,\n );\n }\n\n /** Create a Stripe-like customer (`cus_…`) and zero-balance wallet. */\n createCustomer(params: CreateCustomerParams): Promise<Customer> {\n assertCreateCustomer(params);\n return this.http.request<Customer>('POST', '/v1/customers', {\n name: params.name,\n email: params.email,\n phoneNumber: params.phoneNumber,\n });\n }\n\n /** Issue a top-up session JWT for the frontend (bundles / future deposits). */\n topUp(params: { customerId: string }): Promise<TopUpSession> {\n if (!params?.customerId?.trim()) {\n throw new JazaError('customerId is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n return this.http.request<TopUpSession>('POST', '/v1/top-ups', {\n customerId: params.customerId,\n });\n }\n\n /** Debit credits for a customer (feature code or raw credits). */\n consume(params: ConsumeParams): Promise<ConsumeResult> {\n assertConsume(params);\n return this.http.request<ConsumeResult>('POST', '/v1/credits/consume', {\n customerId: params.customerId,\n idempotencyKey: params.idempotencyKey,\n featureCode: params.featureCode,\n credits: params.credits,\n reason: params.reason,\n });\n }\n\n /** Check whether a top-up session completed (PENDING until PawaPay deposits land). */\n check(params: { topUpId: string }): Promise<TopUpSession> {\n if (!params?.topUpId?.trim()) {\n throw new JazaError('topUpId is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n return this.http.request<TopUpSession>(\n 'GET',\n `/v1/top-ups/${encodeURIComponent(params.topUpId)}`,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
type JazaConfig = {
|
|
2
|
+
/** Merchant secret key (`jz_test_sk_…` / `jz_live_sk_…`) */
|
|
3
|
+
secretKey: string;
|
|
4
|
+
/** Merchant public key (`jz_test_pk_…` / `jz_live_pk_…`) — pass to frontend with top-up JWTs */
|
|
5
|
+
publicKey: string;
|
|
6
|
+
/** Override API host (default `https://api.jaza.dev`) */
|
|
7
|
+
apiBaseUrl?: string;
|
|
8
|
+
};
|
|
9
|
+
type CreateCustomerParams = {
|
|
10
|
+
name: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
phoneNumber?: string;
|
|
13
|
+
};
|
|
14
|
+
type Customer = {
|
|
15
|
+
id: string;
|
|
16
|
+
appId: string;
|
|
17
|
+
name: string;
|
|
18
|
+
email: string | null;
|
|
19
|
+
phoneNumber: string | null;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
};
|
|
23
|
+
type TopUpSessionStatus = 'PENDING' | 'COMPLETED' | 'EXPIRED' | 'CANCELLED';
|
|
24
|
+
type TopUpSession = {
|
|
25
|
+
id: string;
|
|
26
|
+
customerId: string;
|
|
27
|
+
status: TopUpSessionStatus;
|
|
28
|
+
/** Present on create (`topUp`); omit on `check` */
|
|
29
|
+
token?: string;
|
|
30
|
+
expiresAt: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
};
|
|
34
|
+
type ConsumeParams = {
|
|
35
|
+
customerId: string;
|
|
36
|
+
idempotencyKey: string;
|
|
37
|
+
featureCode?: string;
|
|
38
|
+
credits?: number;
|
|
39
|
+
reason?: string;
|
|
40
|
+
};
|
|
41
|
+
type Wallet = {
|
|
42
|
+
id: string;
|
|
43
|
+
appId: string;
|
|
44
|
+
externalUserId: string;
|
|
45
|
+
customerId: string | null;
|
|
46
|
+
balanceCredits: number;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
};
|
|
50
|
+
type LedgerEntry = {
|
|
51
|
+
id: string;
|
|
52
|
+
direction: string;
|
|
53
|
+
amountCredits: number;
|
|
54
|
+
balanceAfter: number;
|
|
55
|
+
reason: string;
|
|
56
|
+
idempotencyKey: string;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
};
|
|
59
|
+
type ConsumeResult = {
|
|
60
|
+
wallet: Wallet;
|
|
61
|
+
ledgerEntry: LedgerEntry;
|
|
62
|
+
};
|
|
63
|
+
type JazaErrorBody = {
|
|
64
|
+
message?: string;
|
|
65
|
+
statusCode?: number;
|
|
66
|
+
error?: string;
|
|
67
|
+
errors?: unknown;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Official Node.js client for Jaza.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const jaza = new Jaza({ secretKey, publicKey });
|
|
76
|
+
* const customer = await jaza.createCustomer({ name: 'Amina', email: 'a@x.com' });
|
|
77
|
+
* const session = await jaza.topUp({ customerId: customer.id });
|
|
78
|
+
* // hand session.token + publicKey to your frontend SDK
|
|
79
|
+
* await jaza.consume({ customerId: customer.id, featureCode: 'SEND_MESSAGE', idempotencyKey: '...' });
|
|
80
|
+
* await jaza.check({ topUpId: session.id });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare class Jaza {
|
|
84
|
+
readonly publicKey: string;
|
|
85
|
+
private readonly http;
|
|
86
|
+
constructor(config: JazaConfig);
|
|
87
|
+
/** Create a Stripe-like customer (`cus_…`) and zero-balance wallet. */
|
|
88
|
+
createCustomer(params: CreateCustomerParams): Promise<Customer>;
|
|
89
|
+
/** Issue a top-up session JWT for the frontend (bundles / future deposits). */
|
|
90
|
+
topUp(params: {
|
|
91
|
+
customerId: string;
|
|
92
|
+
}): Promise<TopUpSession>;
|
|
93
|
+
/** Debit credits for a customer (feature code or raw credits). */
|
|
94
|
+
consume(params: ConsumeParams): Promise<ConsumeResult>;
|
|
95
|
+
/** Check whether a top-up session completed (PENDING until PawaPay deposits land). */
|
|
96
|
+
check(params: {
|
|
97
|
+
topUpId: string;
|
|
98
|
+
}): Promise<TopUpSession>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
declare class JazaError extends Error {
|
|
102
|
+
readonly statusCode: number;
|
|
103
|
+
readonly code: string;
|
|
104
|
+
readonly raw: unknown;
|
|
105
|
+
constructor(message: string, options: {
|
|
106
|
+
statusCode: number;
|
|
107
|
+
code?: string;
|
|
108
|
+
raw?: unknown;
|
|
109
|
+
});
|
|
110
|
+
static fromResponse(statusCode: number, body: unknown): JazaError;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
declare const VERSION = "0.1.0";
|
|
114
|
+
declare const DEFAULT_API_BASE_URL = "https://api.jaza.dev";
|
|
115
|
+
|
|
116
|
+
export { type ConsumeParams, type ConsumeResult, type CreateCustomerParams, type Customer, DEFAULT_API_BASE_URL, Jaza, type JazaConfig, JazaError, type JazaErrorBody, type LedgerEntry, type TopUpSession, type TopUpSessionStatus, VERSION, type Wallet };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
type JazaConfig = {
|
|
2
|
+
/** Merchant secret key (`jz_test_sk_…` / `jz_live_sk_…`) */
|
|
3
|
+
secretKey: string;
|
|
4
|
+
/** Merchant public key (`jz_test_pk_…` / `jz_live_pk_…`) — pass to frontend with top-up JWTs */
|
|
5
|
+
publicKey: string;
|
|
6
|
+
/** Override API host (default `https://api.jaza.dev`) */
|
|
7
|
+
apiBaseUrl?: string;
|
|
8
|
+
};
|
|
9
|
+
type CreateCustomerParams = {
|
|
10
|
+
name: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
phoneNumber?: string;
|
|
13
|
+
};
|
|
14
|
+
type Customer = {
|
|
15
|
+
id: string;
|
|
16
|
+
appId: string;
|
|
17
|
+
name: string;
|
|
18
|
+
email: string | null;
|
|
19
|
+
phoneNumber: string | null;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
};
|
|
23
|
+
type TopUpSessionStatus = 'PENDING' | 'COMPLETED' | 'EXPIRED' | 'CANCELLED';
|
|
24
|
+
type TopUpSession = {
|
|
25
|
+
id: string;
|
|
26
|
+
customerId: string;
|
|
27
|
+
status: TopUpSessionStatus;
|
|
28
|
+
/** Present on create (`topUp`); omit on `check` */
|
|
29
|
+
token?: string;
|
|
30
|
+
expiresAt: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
};
|
|
34
|
+
type ConsumeParams = {
|
|
35
|
+
customerId: string;
|
|
36
|
+
idempotencyKey: string;
|
|
37
|
+
featureCode?: string;
|
|
38
|
+
credits?: number;
|
|
39
|
+
reason?: string;
|
|
40
|
+
};
|
|
41
|
+
type Wallet = {
|
|
42
|
+
id: string;
|
|
43
|
+
appId: string;
|
|
44
|
+
externalUserId: string;
|
|
45
|
+
customerId: string | null;
|
|
46
|
+
balanceCredits: number;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
};
|
|
50
|
+
type LedgerEntry = {
|
|
51
|
+
id: string;
|
|
52
|
+
direction: string;
|
|
53
|
+
amountCredits: number;
|
|
54
|
+
balanceAfter: number;
|
|
55
|
+
reason: string;
|
|
56
|
+
idempotencyKey: string;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
};
|
|
59
|
+
type ConsumeResult = {
|
|
60
|
+
wallet: Wallet;
|
|
61
|
+
ledgerEntry: LedgerEntry;
|
|
62
|
+
};
|
|
63
|
+
type JazaErrorBody = {
|
|
64
|
+
message?: string;
|
|
65
|
+
statusCode?: number;
|
|
66
|
+
error?: string;
|
|
67
|
+
errors?: unknown;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Official Node.js client for Jaza.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const jaza = new Jaza({ secretKey, publicKey });
|
|
76
|
+
* const customer = await jaza.createCustomer({ name: 'Amina', email: 'a@x.com' });
|
|
77
|
+
* const session = await jaza.topUp({ customerId: customer.id });
|
|
78
|
+
* // hand session.token + publicKey to your frontend SDK
|
|
79
|
+
* await jaza.consume({ customerId: customer.id, featureCode: 'SEND_MESSAGE', idempotencyKey: '...' });
|
|
80
|
+
* await jaza.check({ topUpId: session.id });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare class Jaza {
|
|
84
|
+
readonly publicKey: string;
|
|
85
|
+
private readonly http;
|
|
86
|
+
constructor(config: JazaConfig);
|
|
87
|
+
/** Create a Stripe-like customer (`cus_…`) and zero-balance wallet. */
|
|
88
|
+
createCustomer(params: CreateCustomerParams): Promise<Customer>;
|
|
89
|
+
/** Issue a top-up session JWT for the frontend (bundles / future deposits). */
|
|
90
|
+
topUp(params: {
|
|
91
|
+
customerId: string;
|
|
92
|
+
}): Promise<TopUpSession>;
|
|
93
|
+
/** Debit credits for a customer (feature code or raw credits). */
|
|
94
|
+
consume(params: ConsumeParams): Promise<ConsumeResult>;
|
|
95
|
+
/** Check whether a top-up session completed (PENDING until PawaPay deposits land). */
|
|
96
|
+
check(params: {
|
|
97
|
+
topUpId: string;
|
|
98
|
+
}): Promise<TopUpSession>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
declare class JazaError extends Error {
|
|
102
|
+
readonly statusCode: number;
|
|
103
|
+
readonly code: string;
|
|
104
|
+
readonly raw: unknown;
|
|
105
|
+
constructor(message: string, options: {
|
|
106
|
+
statusCode: number;
|
|
107
|
+
code?: string;
|
|
108
|
+
raw?: unknown;
|
|
109
|
+
});
|
|
110
|
+
static fromResponse(statusCode: number, body: unknown): JazaError;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
declare const VERSION = "0.1.0";
|
|
114
|
+
declare const DEFAULT_API_BASE_URL = "https://api.jaza.dev";
|
|
115
|
+
|
|
116
|
+
export { type ConsumeParams, type ConsumeResult, type CreateCustomerParams, type Customer, DEFAULT_API_BASE_URL, Jaza, type JazaConfig, JazaError, type JazaErrorBody, type LedgerEntry, type TopUpSession, type TopUpSessionStatus, VERSION, type Wallet };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var JazaError = class _JazaError extends Error {
|
|
3
|
+
statusCode;
|
|
4
|
+
code;
|
|
5
|
+
raw;
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "JazaError";
|
|
9
|
+
this.statusCode = options.statusCode;
|
|
10
|
+
this.code = options.code ?? "api_error";
|
|
11
|
+
this.raw = options.raw ?? null;
|
|
12
|
+
}
|
|
13
|
+
static fromResponse(statusCode, body) {
|
|
14
|
+
const parsed = body ?? {};
|
|
15
|
+
const message = typeof parsed.message === "string" ? parsed.message : `Jaza API request failed with status ${statusCode}`;
|
|
16
|
+
const code = typeof parsed.error === "string" ? parsed.error : statusCode === 401 ? "unauthorized" : statusCode === 404 ? "not_found" : statusCode === 409 ? "conflict" : statusCode === 400 ? "bad_request" : "api_error";
|
|
17
|
+
return new _JazaError(message, { statusCode, code, raw: body });
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/version.ts
|
|
22
|
+
var VERSION = "0.1.0";
|
|
23
|
+
var DEFAULT_API_BASE_URL = "https://api.jaza.dev";
|
|
24
|
+
|
|
25
|
+
// src/client.ts
|
|
26
|
+
var HttpClient = class {
|
|
27
|
+
constructor(apiBaseUrl, secretKey) {
|
|
28
|
+
this.apiBaseUrl = apiBaseUrl;
|
|
29
|
+
this.secretKey = secretKey;
|
|
30
|
+
}
|
|
31
|
+
apiBaseUrl;
|
|
32
|
+
secretKey;
|
|
33
|
+
async request(method, path, body) {
|
|
34
|
+
const url = `${this.apiBaseUrl.replace(/\/$/, "")}${path}`;
|
|
35
|
+
const headers = {
|
|
36
|
+
Authorization: `Bearer ${this.secretKey}`,
|
|
37
|
+
Accept: "application/json",
|
|
38
|
+
"User-Agent": `@jazadev/node/${VERSION}`
|
|
39
|
+
};
|
|
40
|
+
if (body !== void 0) {
|
|
41
|
+
headers["Content-Type"] = "application/json";
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch(url, {
|
|
44
|
+
method,
|
|
45
|
+
headers,
|
|
46
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
47
|
+
});
|
|
48
|
+
const text = await response.text();
|
|
49
|
+
let parsed = null;
|
|
50
|
+
if (text) {
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(text);
|
|
53
|
+
} catch {
|
|
54
|
+
parsed = { message: text };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw JazaError.fromResponse(response.status, parsed);
|
|
59
|
+
}
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/jaza.ts
|
|
65
|
+
function assertConfig(config) {
|
|
66
|
+
if (!config?.secretKey || typeof config.secretKey !== "string") {
|
|
67
|
+
throw new JazaError("secretKey is required", {
|
|
68
|
+
statusCode: 0,
|
|
69
|
+
code: "invalid_config"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (!config.secretKey.includes("_sk_")) {
|
|
73
|
+
throw new JazaError("secretKey must be a merchant secret key (jz_*_sk_*)", {
|
|
74
|
+
statusCode: 0,
|
|
75
|
+
code: "invalid_config"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (!config?.publicKey || typeof config.publicKey !== "string") {
|
|
79
|
+
throw new JazaError("publicKey is required", {
|
|
80
|
+
statusCode: 0,
|
|
81
|
+
code: "invalid_config"
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (!config.publicKey.includes("_pk_")) {
|
|
85
|
+
throw new JazaError("publicKey must be a merchant public key (jz_*_pk_*)", {
|
|
86
|
+
statusCode: 0,
|
|
87
|
+
code: "invalid_config"
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function assertCreateCustomer(params) {
|
|
92
|
+
if (!params?.name?.trim()) {
|
|
93
|
+
throw new JazaError("name is required", {
|
|
94
|
+
statusCode: 0,
|
|
95
|
+
code: "invalid_request"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (!params.email && !params.phoneNumber) {
|
|
99
|
+
throw new JazaError("email or phoneNumber is required", {
|
|
100
|
+
statusCode: 0,
|
|
101
|
+
code: "invalid_request"
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function assertConsume(params) {
|
|
106
|
+
if (!params?.customerId?.trim()) {
|
|
107
|
+
throw new JazaError("customerId is required", {
|
|
108
|
+
statusCode: 0,
|
|
109
|
+
code: "invalid_request"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (!params.idempotencyKey?.trim()) {
|
|
113
|
+
throw new JazaError("idempotencyKey is required", {
|
|
114
|
+
statusCode: 0,
|
|
115
|
+
code: "invalid_request"
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const hasFeature = Boolean(params.featureCode);
|
|
119
|
+
const hasCredits = params.credits !== void 0;
|
|
120
|
+
if (!hasFeature && !hasCredits) {
|
|
121
|
+
throw new JazaError("featureCode or credits is required", {
|
|
122
|
+
statusCode: 0,
|
|
123
|
+
code: "invalid_request"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (hasFeature && hasCredits) {
|
|
127
|
+
throw new JazaError("Provide featureCode or credits, not both", {
|
|
128
|
+
statusCode: 0,
|
|
129
|
+
code: "invalid_request"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
var Jaza = class {
|
|
134
|
+
publicKey;
|
|
135
|
+
http;
|
|
136
|
+
constructor(config) {
|
|
137
|
+
assertConfig(config);
|
|
138
|
+
this.publicKey = config.publicKey;
|
|
139
|
+
this.http = new HttpClient(
|
|
140
|
+
config.apiBaseUrl ?? DEFAULT_API_BASE_URL,
|
|
141
|
+
config.secretKey
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
/** Create a Stripe-like customer (`cus_…`) and zero-balance wallet. */
|
|
145
|
+
createCustomer(params) {
|
|
146
|
+
assertCreateCustomer(params);
|
|
147
|
+
return this.http.request("POST", "/v1/customers", {
|
|
148
|
+
name: params.name,
|
|
149
|
+
email: params.email,
|
|
150
|
+
phoneNumber: params.phoneNumber
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** Issue a top-up session JWT for the frontend (bundles / future deposits). */
|
|
154
|
+
topUp(params) {
|
|
155
|
+
if (!params?.customerId?.trim()) {
|
|
156
|
+
throw new JazaError("customerId is required", {
|
|
157
|
+
statusCode: 0,
|
|
158
|
+
code: "invalid_request"
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return this.http.request("POST", "/v1/top-ups", {
|
|
162
|
+
customerId: params.customerId
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** Debit credits for a customer (feature code or raw credits). */
|
|
166
|
+
consume(params) {
|
|
167
|
+
assertConsume(params);
|
|
168
|
+
return this.http.request("POST", "/v1/credits/consume", {
|
|
169
|
+
customerId: params.customerId,
|
|
170
|
+
idempotencyKey: params.idempotencyKey,
|
|
171
|
+
featureCode: params.featureCode,
|
|
172
|
+
credits: params.credits,
|
|
173
|
+
reason: params.reason
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** Check whether a top-up session completed (PENDING until PawaPay deposits land). */
|
|
177
|
+
check(params) {
|
|
178
|
+
if (!params?.topUpId?.trim()) {
|
|
179
|
+
throw new JazaError("topUpId is required", {
|
|
180
|
+
statusCode: 0,
|
|
181
|
+
code: "invalid_request"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return this.http.request(
|
|
185
|
+
"GET",
|
|
186
|
+
`/v1/top-ups/${encodeURIComponent(params.topUpId)}`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
export {
|
|
191
|
+
DEFAULT_API_BASE_URL,
|
|
192
|
+
Jaza,
|
|
193
|
+
JazaError,
|
|
194
|
+
VERSION
|
|
195
|
+
};
|
|
196
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/jaza.ts"],"sourcesContent":["import type { JazaErrorBody } from './types.js';\n\nexport class JazaError extends Error {\n readonly statusCode: number;\n readonly code: string;\n readonly raw: unknown;\n\n constructor(message: string, options: {\n statusCode: number;\n code?: string;\n raw?: unknown;\n }) {\n super(message);\n this.name = 'JazaError';\n this.statusCode = options.statusCode;\n this.code = options.code ?? 'api_error';\n this.raw = options.raw ?? null;\n }\n\n static fromResponse(statusCode: number, body: unknown): JazaError {\n const parsed = (body ?? {}) as JazaErrorBody;\n const message =\n typeof parsed.message === 'string'\n ? parsed.message\n : `Jaza API request failed with status ${statusCode}`;\n const code =\n typeof parsed.error === 'string'\n ? parsed.error\n : statusCode === 401\n ? 'unauthorized'\n : statusCode === 404\n ? 'not_found'\n : statusCode === 409\n ? 'conflict'\n : statusCode === 400\n ? 'bad_request'\n : 'api_error';\n return new JazaError(message, { statusCode, code, raw: body });\n }\n}\n","export const VERSION = '0.1.0';\n\nexport const DEFAULT_API_BASE_URL = 'https://api.jaza.dev';\n","import { JazaError } from './errors.js';\nimport { VERSION } from './version.js';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\nexport class HttpClient {\n constructor(\n private readonly apiBaseUrl: string,\n private readonly secretKey: string,\n ) {}\n\n async request<T>(\n method: HttpMethod,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${this.apiBaseUrl.replace(/\\/$/, '')}${path}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.secretKey}`,\n Accept: 'application/json',\n 'User-Agent': `@jazadev/node/${VERSION}`,\n };\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n const text = await response.text();\n let parsed: unknown = null;\n if (text) {\n try {\n parsed = JSON.parse(text) as unknown;\n } catch {\n parsed = { message: text };\n }\n }\n\n if (!response.ok) {\n throw JazaError.fromResponse(response.status, parsed);\n }\n\n return parsed as T;\n }\n}\n","import { HttpClient } from './client.js';\nimport { JazaError } from './errors.js';\nimport type {\n ConsumeParams,\n ConsumeResult,\n CreateCustomerParams,\n Customer,\n JazaConfig,\n TopUpSession,\n} from './types.js';\nimport { DEFAULT_API_BASE_URL } from './version.js';\n\nfunction assertConfig(config: JazaConfig): void {\n if (!config?.secretKey || typeof config.secretKey !== 'string') {\n throw new JazaError('secretKey is required', {\n statusCode: 0,\n code: 'invalid_config',\n });\n }\n if (!config.secretKey.includes('_sk_')) {\n throw new JazaError('secretKey must be a merchant secret key (jz_*_sk_*)', {\n statusCode: 0,\n code: 'invalid_config',\n });\n }\n if (!config?.publicKey || typeof config.publicKey !== 'string') {\n throw new JazaError('publicKey is required', {\n statusCode: 0,\n code: 'invalid_config',\n });\n }\n if (!config.publicKey.includes('_pk_')) {\n throw new JazaError('publicKey must be a merchant public key (jz_*_pk_*)', {\n statusCode: 0,\n code: 'invalid_config',\n });\n }\n}\n\nfunction assertCreateCustomer(params: CreateCustomerParams): void {\n if (!params?.name?.trim()) {\n throw new JazaError('name is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n if (!params.email && !params.phoneNumber) {\n throw new JazaError('email or phoneNumber is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n}\n\nfunction assertConsume(params: ConsumeParams): void {\n if (!params?.customerId?.trim()) {\n throw new JazaError('customerId is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n if (!params.idempotencyKey?.trim()) {\n throw new JazaError('idempotencyKey is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n const hasFeature = Boolean(params.featureCode);\n const hasCredits = params.credits !== undefined;\n if (!hasFeature && !hasCredits) {\n throw new JazaError('featureCode or credits is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n if (hasFeature && hasCredits) {\n throw new JazaError('Provide featureCode or credits, not both', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n}\n\n/**\n * Official Node.js client for Jaza.\n *\n * @example\n * ```ts\n * const jaza = new Jaza({ secretKey, publicKey });\n * const customer = await jaza.createCustomer({ name: 'Amina', email: 'a@x.com' });\n * const session = await jaza.topUp({ customerId: customer.id });\n * // hand session.token + publicKey to your frontend SDK\n * await jaza.consume({ customerId: customer.id, featureCode: 'SEND_MESSAGE', idempotencyKey: '...' });\n * await jaza.check({ topUpId: session.id });\n * ```\n */\nexport class Jaza {\n readonly publicKey: string;\n private readonly http: HttpClient;\n\n constructor(config: JazaConfig) {\n assertConfig(config);\n this.publicKey = config.publicKey;\n this.http = new HttpClient(\n config.apiBaseUrl ?? DEFAULT_API_BASE_URL,\n config.secretKey,\n );\n }\n\n /** Create a Stripe-like customer (`cus_…`) and zero-balance wallet. */\n createCustomer(params: CreateCustomerParams): Promise<Customer> {\n assertCreateCustomer(params);\n return this.http.request<Customer>('POST', '/v1/customers', {\n name: params.name,\n email: params.email,\n phoneNumber: params.phoneNumber,\n });\n }\n\n /** Issue a top-up session JWT for the frontend (bundles / future deposits). */\n topUp(params: { customerId: string }): Promise<TopUpSession> {\n if (!params?.customerId?.trim()) {\n throw new JazaError('customerId is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n return this.http.request<TopUpSession>('POST', '/v1/top-ups', {\n customerId: params.customerId,\n });\n }\n\n /** Debit credits for a customer (feature code or raw credits). */\n consume(params: ConsumeParams): Promise<ConsumeResult> {\n assertConsume(params);\n return this.http.request<ConsumeResult>('POST', '/v1/credits/consume', {\n customerId: params.customerId,\n idempotencyKey: params.idempotencyKey,\n featureCode: params.featureCode,\n credits: params.credits,\n reason: params.reason,\n });\n }\n\n /** Check whether a top-up session completed (PENDING until PawaPay deposits land). */\n check(params: { topUpId: string }): Promise<TopUpSession> {\n if (!params?.topUpId?.trim()) {\n throw new JazaError('topUpId is required', {\n statusCode: 0,\n code: 'invalid_request',\n });\n }\n return this.http.request<TopUpSession>(\n 'GET',\n `/v1/top-ups/${encodeURIComponent(params.topUpId)}`,\n );\n }\n}\n"],"mappings":";AAEO,IAAM,YAAN,MAAM,mBAAkB,MAAM;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,SAI1B;AACD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa,QAAQ;AAC1B,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,MAAM,QAAQ,OAAO;AAAA,EAC5B;AAAA,EAEA,OAAO,aAAa,YAAoB,MAA0B;AAChE,UAAM,SAAU,QAAQ,CAAC;AACzB,UAAM,UACJ,OAAO,OAAO,YAAY,WACtB,OAAO,UACP,uCAAuC,UAAU;AACvD,UAAM,OACJ,OAAO,OAAO,UAAU,WACpB,OAAO,QACP,eAAe,MACb,iBACA,eAAe,MACb,cACA,eAAe,MACb,aACA,eAAe,MACb,gBACA;AACd,WAAO,IAAI,WAAU,SAAS,EAAE,YAAY,MAAM,KAAK,KAAK,CAAC;AAAA,EAC/D;AACF;;;ACvCO,IAAM,UAAU;AAEhB,IAAM,uBAAuB;;;ACG7B,IAAM,aAAN,MAAiB;AAAA,EACtB,YACmB,YACA,WACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EAGnB,MAAM,QACJ,QACA,MACA,MACY;AACZ,UAAM,MAAM,GAAG,KAAK,WAAW,QAAQ,OAAO,EAAE,CAAC,GAAG,IAAI;AACxD,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,SAAS;AAAA,MACvC,QAAQ;AAAA,MACR,cAAc,iBAAiB,OAAO;AAAA,IACxC;AACA,QAAI,SAAS,QAAW;AACtB,cAAQ,cAAc,IAAI;AAAA,IAC5B;AAEA,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC;AAAA,MACA;AAAA,MACA,MAAM,SAAS,SAAY,SAAY,KAAK,UAAU,IAAI;AAAA,IAC5D,CAAC;AAED,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,SAAkB;AACtB,QAAI,MAAM;AACR,UAAI;AACF,iBAAS,KAAK,MAAM,IAAI;AAAA,MAC1B,QAAQ;AACN,iBAAS,EAAE,SAAS,KAAK;AAAA,MAC3B;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,UAAU,aAAa,SAAS,QAAQ,MAAM;AAAA,IACtD;AAEA,WAAO;AAAA,EACT;AACF;;;ACpCA,SAAS,aAAa,QAA0B;AAC9C,MAAI,CAAC,QAAQ,aAAa,OAAO,OAAO,cAAc,UAAU;AAC9D,UAAM,IAAI,UAAU,yBAAyB;AAAA,MAC3C,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,MAAI,CAAC,OAAO,UAAU,SAAS,MAAM,GAAG;AACtC,UAAM,IAAI,UAAU,uDAAuD;AAAA,MACzE,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,MAAI,CAAC,QAAQ,aAAa,OAAO,OAAO,cAAc,UAAU;AAC9D,UAAM,IAAI,UAAU,yBAAyB;AAAA,MAC3C,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,MAAI,CAAC,OAAO,UAAU,SAAS,MAAM,GAAG;AACtC,UAAM,IAAI,UAAU,uDAAuD;AAAA,MACzE,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACF;AAEA,SAAS,qBAAqB,QAAoC;AAChE,MAAI,CAAC,QAAQ,MAAM,KAAK,GAAG;AACzB,UAAM,IAAI,UAAU,oBAAoB;AAAA,MACtC,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,MAAI,CAAC,OAAO,SAAS,CAAC,OAAO,aAAa;AACxC,UAAM,IAAI,UAAU,oCAAoC;AAAA,MACtD,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACF;AAEA,SAAS,cAAc,QAA6B;AAClD,MAAI,CAAC,QAAQ,YAAY,KAAK,GAAG;AAC/B,UAAM,IAAI,UAAU,0BAA0B;AAAA,MAC5C,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,MAAI,CAAC,OAAO,gBAAgB,KAAK,GAAG;AAClC,UAAM,IAAI,UAAU,8BAA8B;AAAA,MAChD,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,QAAM,aAAa,QAAQ,OAAO,WAAW;AAC7C,QAAM,aAAa,OAAO,YAAY;AACtC,MAAI,CAAC,cAAc,CAAC,YAAY;AAC9B,UAAM,IAAI,UAAU,sCAAsC;AAAA,MACxD,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,MAAI,cAAc,YAAY;AAC5B,UAAM,IAAI,UAAU,4CAA4C;AAAA,MAC9D,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACF;AAeO,IAAM,OAAN,MAAW;AAAA,EACP;AAAA,EACQ;AAAA,EAEjB,YAAY,QAAoB;AAC9B,iBAAa,MAAM;AACnB,SAAK,YAAY,OAAO;AACxB,SAAK,OAAO,IAAI;AAAA,MACd,OAAO,cAAc;AAAA,MACrB,OAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,eAAe,QAAiD;AAC9D,yBAAqB,MAAM;AAC3B,WAAO,KAAK,KAAK,QAAkB,QAAQ,iBAAiB;AAAA,MAC1D,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAuD;AAC3D,QAAI,CAAC,QAAQ,YAAY,KAAK,GAAG;AAC/B,YAAM,IAAI,UAAU,0BAA0B;AAAA,QAC5C,YAAY;AAAA,QACZ,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,WAAO,KAAK,KAAK,QAAsB,QAAQ,eAAe;AAAA,MAC5D,YAAY,OAAO;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,QAAQ,QAA+C;AACrD,kBAAc,MAAM;AACpB,WAAO,KAAK,KAAK,QAAuB,QAAQ,uBAAuB;AAAA,MACrE,YAAY,OAAO;AAAA,MACnB,gBAAgB,OAAO;AAAA,MACvB,aAAa,OAAO;AAAA,MACpB,SAAS,OAAO;AAAA,MAChB,QAAQ,OAAO;AAAA,IACjB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAoD;AACxD,QAAI,CAAC,QAAQ,SAAS,KAAK,GAAG;AAC5B,YAAM,IAAI,UAAU,uBAAuB;AAAA,QACzC,YAAY;AAAA,QACZ,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,WAAO,KAAK,KAAK;AAAA,MACf;AAAA,MACA,eAAe,mBAAmB,OAAO,OAAO,CAAC;AAAA,IACnD;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jazadev/node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official Node.js SDK for Jaza prepaid / metered billing",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Jaza",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"lint": "tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^24.0.0",
|
|
34
|
+
"tsup": "^8.5.0",
|
|
35
|
+
"typescript": "^5.7.3",
|
|
36
|
+
"vitest": "^3.2.4"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"jaza",
|
|
40
|
+
"billing",
|
|
41
|
+
"credits",
|
|
42
|
+
"momo",
|
|
43
|
+
"sdk"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/josumung999/jaza-packages.git",
|
|
51
|
+
"directory": "@jaza-node"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/josumung999/jaza-packages/tree/main/@jaza-node",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/josumung999/jaza-packages/issues"
|
|
56
|
+
}
|
|
57
|
+
}
|