remita-checkout-client 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/dist/cli.js ADDED
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/cli.ts
5
+ var import_node_util = require("util");
6
+
7
+ // src/http-client.ts
8
+ var RemitaApiError = class extends Error {
9
+ constructor(message, status, payload) {
10
+ super(message);
11
+ this.status = status;
12
+ this.payload = payload;
13
+ this.name = "RemitaApiError";
14
+ }
15
+ };
16
+ var HttpClient = class {
17
+ constructor(options) {
18
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
19
+ this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
20
+ this.timeoutMs = options.timeoutMs ?? 3e4;
21
+ this.defaultHeaders = options.defaultHeaders ?? {};
22
+ if (!this.fetchImpl) {
23
+ throw new Error("No fetch implementation available for HttpClient.");
24
+ }
25
+ }
26
+ async request(method, path, opts = {}) {
27
+ const url = opts.absoluteUrl ? opts.absoluteUrl : `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
28
+ const headers = {
29
+ Accept: "application/json",
30
+ ...this.defaultHeaders,
31
+ ...opts.headers
32
+ };
33
+ let body;
34
+ if (opts.body !== void 0) {
35
+ if (opts.contentType === "application/x-www-form-urlencoded") {
36
+ headers["Content-Type"] = opts.contentType;
37
+ body = new URLSearchParams(
38
+ Object.entries(opts.body).map(([k, v]) => [
39
+ k,
40
+ String(v)
41
+ ])
42
+ ).toString();
43
+ } else {
44
+ headers["Content-Type"] = opts.contentType ?? "application/json";
45
+ body = JSON.stringify(opts.body);
46
+ }
47
+ }
48
+ const controller = new AbortController();
49
+ const timer = this.timeoutMs > 0 ? setTimeout(() => controller.abort(), this.timeoutMs) : void 0;
50
+ try {
51
+ const response = await this.fetchImpl(url, {
52
+ method,
53
+ headers,
54
+ body,
55
+ signal: controller.signal
56
+ });
57
+ const text = await response.text();
58
+ let parsed = text;
59
+ try {
60
+ parsed = text ? JSON.parse(text) : void 0;
61
+ } catch {
62
+ }
63
+ if (!response.ok) {
64
+ const payload = parsed ?? {};
65
+ const message = payload.responseMessage ?? payload.message ?? `Request failed with status ${response.status}`;
66
+ throw new RemitaApiError(message, response.status, payload);
67
+ }
68
+ return parsed;
69
+ } finally {
70
+ if (timer) clearTimeout(timer);
71
+ }
72
+ }
73
+ };
74
+
75
+ // src/environment.ts
76
+ var ENVIRONMENT_BASE_URLS = {
77
+ qa: "https://api-checkout-qa.systemspecsng.com/api/v1",
78
+ demo: "https://api-checkout-demo.systemspecsng.com/api/v1",
79
+ production: "https://api-checkout.systemspecsng.com/api/v1"
80
+ };
81
+ var PREFIX_RULES = [
82
+ { pattern: /^(qa[-_]|pk_qa_)/i, environment: "qa" },
83
+ { pattern: /^(demo[-_]|pk_demo_|pk_test_)/i, environment: "demo" }
84
+ ];
85
+ function resolveEnvironment(publicKey, options = {}) {
86
+ const baseUrls = { ...ENVIRONMENT_BASE_URLS, ...options.baseUrls };
87
+ if (options.environment) {
88
+ return {
89
+ environment: options.environment,
90
+ baseUrl: baseUrls[options.environment],
91
+ detected: true
92
+ };
93
+ }
94
+ for (const rule of PREFIX_RULES) {
95
+ if (rule.pattern.test(publicKey)) {
96
+ return {
97
+ environment: rule.environment,
98
+ baseUrl: baseUrls[rule.environment],
99
+ detected: true
100
+ };
101
+ }
102
+ }
103
+ return { environment: "qa", baseUrl: baseUrls.qa, detected: false };
104
+ }
105
+
106
+ // src/stream.ts
107
+ function openStatusStream(baseUrl, trxRef, publicKey, options) {
108
+ const fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
109
+ if (!fetchImpl) {
110
+ throw new Error("No fetch implementation available for streamPaymentStatus.");
111
+ }
112
+ const url = `${baseUrl.replace(/\/+$/, "")}/payment/status/stream/${encodeURIComponent(
113
+ trxRef
114
+ )}?publicKey=${encodeURIComponent(publicKey)}`;
115
+ const headers = { Accept: "text/event-stream" };
116
+ if (options.sendPublicKeyHeader) {
117
+ headers.publicKey = publicKey;
118
+ }
119
+ const controller = new AbortController();
120
+ (async () => {
121
+ try {
122
+ const response = await fetchImpl(url, {
123
+ headers,
124
+ signal: controller.signal
125
+ });
126
+ if (!response.ok || !response.body) {
127
+ throw new Error(`Status stream request failed with status ${response.status}`);
128
+ }
129
+ options.onOpen?.();
130
+ const reader = response.body.getReader();
131
+ const decoder = new TextDecoder();
132
+ let buffer = "";
133
+ while (true) {
134
+ const { done, value } = await reader.read();
135
+ if (done) break;
136
+ buffer += decoder.decode(value, { stream: true });
137
+ const events = buffer.split("\n\n");
138
+ buffer = events.pop() ?? "";
139
+ for (const rawEvent of events) {
140
+ const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
141
+ if (dataLines.length === 0) continue;
142
+ const raw = dataLines.join("\n");
143
+ let data = raw;
144
+ try {
145
+ data = JSON.parse(raw);
146
+ } catch {
147
+ }
148
+ options.onMessage({ data, raw });
149
+ }
150
+ }
151
+ } catch (err) {
152
+ if (err instanceof Error && err.name === "AbortError") return;
153
+ options.onError?.(err);
154
+ }
155
+ })();
156
+ return {
157
+ close: () => controller.abort()
158
+ };
159
+ }
160
+
161
+ // src/client.ts
162
+ var RemitaCheckoutClient = class {
163
+ constructor(options) {
164
+ this.publicKey = options.publicKey;
165
+ const resolved = resolveEnvironment(options.publicKey, {
166
+ environment: options.environment,
167
+ baseUrls: options.baseUrls
168
+ });
169
+ if (!resolved.detected) {
170
+ options.onEnvironmentDetectionFallback?.("qa");
171
+ }
172
+ this.environment = resolved.environment;
173
+ this.baseUrl = resolved.baseUrl;
174
+ this.http = new HttpClient({
175
+ baseUrl: resolved.baseUrl,
176
+ fetchImpl: options.fetchImpl,
177
+ timeoutMs: options.timeoutMs,
178
+ defaultHeaders: {
179
+ // publicKey is required as a header on every checkout API call.
180
+ publicKey: options.publicKey,
181
+ ...options.defaultHeaders
182
+ }
183
+ });
184
+ }
185
+ // --- Core payment lifecycle ---------------------------------------------
186
+ prePayment(request) {
187
+ return this.http.request("POST", "/payment/pre", { body: request });
188
+ }
189
+ calculateFee(request) {
190
+ return this.http.request("POST", "/payment/pre/feeCharge", { body: request });
191
+ }
192
+ initiatePayment(request) {
193
+ return this.http.request("POST", "/payment/initiate", { body: request });
194
+ }
195
+ /**
196
+ * Posts authorization params (OTP, PIN, 3DS form fields, etc) to the
197
+ * processor-provided actionUrl from initiatePayment().
198
+ *
199
+ * NOTE: CyberSource/Cardinal 3DS endpoints expect
200
+ * `application/x-www-form-urlencoded`. Pass `contentType` if the
201
+ * actionUrl is a 3DS processor endpoint.
202
+ */
203
+ authorize(actionUrl, payload, contentType) {
204
+ void contentType;
205
+ return this.http.request("POST", "", {
206
+ body: payload,
207
+ absoluteUrl: actionUrl
208
+ });
209
+ }
210
+ /**
211
+ * Validates second-factor authorization (e.g. OTP). Per the endpoint docs
212
+ * this may go to a fixed /payment/validate endpoint OR to a dynamic
213
+ * actionUrl — pass `actionUrl` if the preceding step returned one.
214
+ */
215
+ validatePayment(request, actionUrl) {
216
+ return actionUrl ? this.http.request("POST", "", { body: request, absoluteUrl: actionUrl }) : this.http.request("POST", "/payment/validate", { body: request });
217
+ }
218
+ verifyPayment(paymentRef) {
219
+ return this.http.request("GET", `/payment/verify/${encodeURIComponent(paymentRef)}`);
220
+ }
221
+ /** Opens an SSE stream for real-time status updates. Call the returned `close()` when done. */
222
+ streamPaymentStatus(trxRef, options) {
223
+ return openStatusStream(this.baseUrl, trxRef, this.publicKey, options);
224
+ }
225
+ // --- Channel data / configuration ---------------------------------------
226
+ getBankList() {
227
+ return this.http.request("GET", "/payment/bank-list");
228
+ }
229
+ getUssdCodes() {
230
+ return this.http.request("GET", "/misc/ussd-codes");
231
+ }
232
+ getWalletTypes() {
233
+ return this.http.request("GET", "/misc/wallet-type");
234
+ }
235
+ };
236
+
237
+ // src/cli.ts
238
+ function printUsage() {
239
+ console.log(`
240
+ remita-checkout <command> [options]
241
+
242
+ Commands:
243
+ pre Pre-payment initialization
244
+ fee Calculate channel fee
245
+ initiate Initiate a payment
246
+ authorize Submit authorization params to an actionUrl
247
+ validate Validate second-factor authorization (e.g. OTP)
248
+ verify Verify payment status by reference
249
+ stream Stream real-time status updates (SSE) for a transaction
250
+ banks List supported banks
251
+ ussd List USSD codes
252
+ wallets List wallet types
253
+
254
+ Global options:
255
+ --key <publicKey> Remita public key (required for most commands)
256
+ --env <qa|demo|production> Explicit environment (skips prefix detection)
257
+ --base-url <url> Override the resolved base URL entirely
258
+ --json Print raw JSON only, useful for piping
259
+
260
+ Command-specific options:
261
+ pre: --rrr <rrr> | --txn <transactionId> [--payload '<json>']
262
+ fee: --amount <amount> --channel <channel>
263
+ initiate: --amount <amount> --channel <channel> [--txn <transactionId>]
264
+ authorize: --action-url <url> --payload '<json>'
265
+ validate: --payload '<json>' [--action-url <url>] [--ref <paymentReference>] [--otp <otp>]
266
+ verify: --ref <paymentReference>
267
+ stream: --trx <trxRef>
268
+
269
+ Examples:
270
+ remita-checkout pre --key QA-abc123 --rrr 123456789
271
+ remita-checkout initiate --key QA-abc123 --amount 5000 --channel card --json
272
+ remita-checkout stream --key QA-abc123 --trx TRX-001
273
+ `);
274
+ }
275
+ function buildClient(values) {
276
+ const key = values.key;
277
+ if (!key) throw new Error("--key is required.");
278
+ const baseUrl = values["base-url"];
279
+ return new RemitaCheckoutClient({
280
+ publicKey: key,
281
+ environment: values.env,
282
+ baseUrls: baseUrl ? { qa: baseUrl, demo: baseUrl, production: baseUrl } : void 0,
283
+ onEnvironmentDetectionFallback: (guessed) => {
284
+ if (!values.json) {
285
+ console.error(`(couldn't detect environment from key prefix, defaulting to "${guessed}")`);
286
+ }
287
+ }
288
+ });
289
+ }
290
+ function output(values, label, data) {
291
+ if (values.json) {
292
+ console.log(JSON.stringify(data, null, 2));
293
+ return;
294
+ }
295
+ console.log(`
296
+ ${label}:`);
297
+ console.log(JSON.stringify(data, null, 2));
298
+ }
299
+ function parsePayload(raw) {
300
+ if (!raw) return {};
301
+ try {
302
+ return JSON.parse(raw);
303
+ } catch {
304
+ throw new Error(`--payload must be valid JSON, e.g. --payload '{"otp":"123456"}'`);
305
+ }
306
+ }
307
+ async function main() {
308
+ const { values, positionals } = (0, import_node_util.parseArgs)({
309
+ allowPositionals: true,
310
+ options: {
311
+ key: { type: "string" },
312
+ env: { type: "string" },
313
+ "base-url": { type: "string" },
314
+ json: { type: "boolean", default: false },
315
+ rrr: { type: "string" },
316
+ txn: { type: "string" },
317
+ amount: { type: "string" },
318
+ channel: { type: "string" },
319
+ "action-url": { type: "string" },
320
+ payload: { type: "string" },
321
+ ref: { type: "string" },
322
+ otp: { type: "string" },
323
+ trx: { type: "string" },
324
+ help: { type: "boolean", default: false }
325
+ }
326
+ });
327
+ const command = positionals[0];
328
+ if (!command || values.help) {
329
+ printUsage();
330
+ process.exit(command ? 0 : 1);
331
+ }
332
+ try {
333
+ switch (command) {
334
+ case "pre": {
335
+ const client = buildClient(values);
336
+ const result = await client.prePayment({
337
+ ...parsePayload(values.payload),
338
+ publicKey: client.publicKey,
339
+ rrr: values.rrr,
340
+ transactionId: values.txn
341
+ });
342
+ output(values, "Pre-payment response", result);
343
+ break;
344
+ }
345
+ case "fee": {
346
+ const client = buildClient(values);
347
+ if (!values.amount || !values.channel) {
348
+ throw new Error("fee requires --amount and --channel.");
349
+ }
350
+ const result = await client.calculateFee({
351
+ publicKey: client.publicKey,
352
+ amount: Number(values.amount),
353
+ channel: values.channel
354
+ });
355
+ output(values, "Fee response", result);
356
+ break;
357
+ }
358
+ case "initiate": {
359
+ const client = buildClient(values);
360
+ if (!values.amount || !values.channel) {
361
+ throw new Error("initiate requires --amount and --channel.");
362
+ }
363
+ const result = await client.initiatePayment({
364
+ publicKey: client.publicKey,
365
+ amount: Number(values.amount),
366
+ channel: values.channel,
367
+ transactionId: values.txn
368
+ });
369
+ output(values, "Initiate response", result);
370
+ break;
371
+ }
372
+ case "authorize": {
373
+ const client = buildClient(values);
374
+ const actionUrl = values["action-url"];
375
+ if (!actionUrl) throw new Error("authorize requires --action-url.");
376
+ const result = await client.authorize(
377
+ actionUrl,
378
+ parsePayload(values.payload)
379
+ );
380
+ output(values, "Authorize response", result);
381
+ break;
382
+ }
383
+ case "validate": {
384
+ const client = buildClient(values);
385
+ const payload = parsePayload(values.payload);
386
+ const result = await client.validatePayment(
387
+ {
388
+ paymentReference: values.ref,
389
+ otp: values.otp,
390
+ ...payload
391
+ },
392
+ values["action-url"]
393
+ );
394
+ output(values, "Validate response", result);
395
+ break;
396
+ }
397
+ case "verify": {
398
+ const client = buildClient(values);
399
+ const ref = values.ref;
400
+ if (!ref) throw new Error("verify requires --ref.");
401
+ const result = await client.verifyPayment(ref);
402
+ output(values, "Verify response", result);
403
+ break;
404
+ }
405
+ case "stream": {
406
+ const client = buildClient(values);
407
+ const trx = values.trx;
408
+ if (!trx) throw new Error("stream requires --trx.");
409
+ console.error(`Streaming status for ${trx}. Ctrl+C to stop.`);
410
+ const handle = client.streamPaymentStatus(trx, {
411
+ onOpen: () => console.error("(stream open)"),
412
+ onMessage: (event) => console.log(JSON.stringify(event.data, null, 2)),
413
+ onError: (err) => {
414
+ console.error("Stream error:", err);
415
+ process.exitCode = 1;
416
+ }
417
+ });
418
+ process.on("SIGINT", () => {
419
+ handle.close();
420
+ process.exit(0);
421
+ });
422
+ await new Promise(() => {
423
+ });
424
+ break;
425
+ }
426
+ case "banks": {
427
+ const client = buildClient(values);
428
+ output(values, "Bank list", await client.getBankList());
429
+ break;
430
+ }
431
+ case "ussd": {
432
+ const client = buildClient(values);
433
+ output(values, "USSD codes", await client.getUssdCodes());
434
+ break;
435
+ }
436
+ case "wallets": {
437
+ const client = buildClient(values);
438
+ output(values, "Wallet types", await client.getWalletTypes());
439
+ break;
440
+ }
441
+ default:
442
+ console.error(`Unknown command: ${command}
443
+ `);
444
+ printUsage();
445
+ process.exit(1);
446
+ }
447
+ } catch (err) {
448
+ console.error("Error:", err instanceof Error ? err.message : err);
449
+ process.exitCode = 1;
450
+ }
451
+ }
452
+ main();
453
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/http-client.ts","../src/environment.ts","../src/stream.ts","../src/client.ts"],"sourcesContent":["#!/usr/bin/env node\n// Node.js provides this builtin at runtime; the project does not currently\n// include Node.js type definitions, so suppress only this module-resolution\n// diagnostic until @types/node is added.\n// @ts-ignore\nimport { parseArgs } from \"node:util\";\nimport { RemitaCheckoutClient } from \"./client\";\nimport type { RemitaEnvironment } from \"./environment\";\n\ndeclare const process: {\n exit(code?: number): never;\n exitCode?: number;\n on(signal: string, listener: () => void): void;\n};\n\nfunction printUsage(): void {\n console.log(`\nremita-checkout <command> [options]\n\nCommands:\n pre Pre-payment initialization\n fee Calculate channel fee\n initiate Initiate a payment\n authorize Submit authorization params to an actionUrl\n validate Validate second-factor authorization (e.g. OTP)\n verify Verify payment status by reference\n stream Stream real-time status updates (SSE) for a transaction\n banks List supported banks\n ussd List USSD codes\n wallets List wallet types\n\nGlobal options:\n --key <publicKey> Remita public key (required for most commands)\n --env <qa|demo|production> Explicit environment (skips prefix detection)\n --base-url <url> Override the resolved base URL entirely\n --json Print raw JSON only, useful for piping\n\nCommand-specific options:\n pre: --rrr <rrr> | --txn <transactionId> [--payload '<json>']\n fee: --amount <amount> --channel <channel>\n initiate: --amount <amount> --channel <channel> [--txn <transactionId>]\n authorize: --action-url <url> --payload '<json>'\n validate: --payload '<json>' [--action-url <url>] [--ref <paymentReference>] [--otp <otp>]\n verify: --ref <paymentReference>\n stream: --trx <trxRef>\n\nExamples:\n remita-checkout pre --key QA-abc123 --rrr 123456789\n remita-checkout initiate --key QA-abc123 --amount 5000 --channel card --json\n remita-checkout stream --key QA-abc123 --trx TRX-001\n`);\n}\n\ntype ParsedValues = Record<string, string | boolean | undefined>;\n\nfunction buildClient(values: ParsedValues): RemitaCheckoutClient {\n const key = values.key as string | undefined;\n if (!key) throw new Error(\"--key is required.\");\n\n const baseUrl = values[\"base-url\"] as string | undefined;\n\n return new RemitaCheckoutClient({\n publicKey: key,\n environment: values.env as RemitaEnvironment | undefined,\n baseUrls: baseUrl ? { qa: baseUrl, demo: baseUrl, production: baseUrl } : undefined,\n onEnvironmentDetectionFallback: (guessed) => {\n if (!values.json) {\n console.error(`(couldn't detect environment from key prefix, defaulting to \"${guessed}\")`);\n }\n },\n });\n}\n\nfunction output(values: ParsedValues, label: string, data: unknown): void {\n if (values.json) {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n console.log(`\\n${label}:`);\n console.log(JSON.stringify(data, null, 2));\n}\n\nfunction parsePayload(raw: string | undefined): Record<string, unknown> {\n if (!raw) return {};\n try {\n return JSON.parse(raw);\n } catch {\n throw new Error(`--payload must be valid JSON, e.g. --payload '{\"otp\":\"123456\"}'`);\n }\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n key: { type: \"string\" },\n env: { type: \"string\" },\n \"base-url\": { type: \"string\" },\n json: { type: \"boolean\", default: false },\n rrr: { type: \"string\" },\n txn: { type: \"string\" },\n amount: { type: \"string\" },\n channel: { type: \"string\" },\n \"action-url\": { type: \"string\" },\n payload: { type: \"string\" },\n ref: { type: \"string\" },\n otp: { type: \"string\" },\n trx: { type: \"string\" },\n help: { type: \"boolean\", default: false },\n },\n });\n\n const command = positionals[0];\n\n if (!command || values.help) {\n printUsage();\n process.exit(command ? 0 : 1);\n }\n\n try {\n switch (command) {\n case \"pre\": {\n const client = buildClient(values);\n const result = await client.prePayment({\n ...parsePayload(values.payload as string | undefined),\n publicKey: client.publicKey,\n rrr: values.rrr as string | undefined,\n transactionId: values.txn as string | undefined,\n } as unknown as Parameters<RemitaCheckoutClient[\"prePayment\"]>[0]);\n output(values, \"Pre-payment response\", result);\n break;\n }\n\n case \"fee\": {\n const client = buildClient(values);\n if (!values.amount || !values.channel) {\n throw new Error(\"fee requires --amount and --channel.\");\n }\n const result = await client.calculateFee({\n publicKey: client.publicKey,\n amount: Number(values.amount),\n channel: values.channel as string,\n });\n output(values, \"Fee response\", result);\n break;\n }\n\n case \"initiate\": {\n const client = buildClient(values);\n if (!values.amount || !values.channel) {\n throw new Error(\"initiate requires --amount and --channel.\");\n }\n const result = await client.initiatePayment({\n publicKey: client.publicKey,\n amount: Number(values.amount),\n channel: values.channel as string,\n transactionId: values.txn as string | undefined,\n });\n output(values, \"Initiate response\", result);\n break;\n }\n\n case \"authorize\": {\n const client = buildClient(values);\n const actionUrl = values[\"action-url\"] as string | undefined;\n if (!actionUrl) throw new Error(\"authorize requires --action-url.\");\n const result = await client.authorize(\n actionUrl,\n parsePayload(values.payload as string | undefined)\n );\n output(values, \"Authorize response\", result);\n break;\n }\n\n case \"validate\": {\n const client = buildClient(values);\n const payload = parsePayload(values.payload as string | undefined);\n const result = await client.validatePayment(\n {\n paymentReference: values.ref as string | undefined,\n otp: values.otp as string | undefined,\n ...payload,\n },\n values[\"action-url\"] as string | undefined\n );\n output(values, \"Validate response\", result);\n break;\n }\n\n case \"verify\": {\n const client = buildClient(values);\n const ref = values.ref as string | undefined;\n if (!ref) throw new Error(\"verify requires --ref.\");\n const result = await client.verifyPayment(ref);\n output(values, \"Verify response\", result);\n break;\n }\n\n case \"stream\": {\n const client = buildClient(values);\n const trx = values.trx as string | undefined;\n if (!trx) throw new Error(\"stream requires --trx.\");\n console.error(`Streaming status for ${trx}. Ctrl+C to stop.`);\n const handle = client.streamPaymentStatus(trx, {\n onOpen: () => console.error(\"(stream open)\"),\n onMessage: (event) => console.log(JSON.stringify(event.data, null, 2)),\n onError: (err) => {\n console.error(\"Stream error:\", err);\n process.exitCode = 1;\n },\n });\n process.on(\"SIGINT\", () => {\n handle.close();\n process.exit(0);\n });\n await new Promise(() => {}); // keep the process alive until Ctrl+C\n break;\n }\n\n case \"banks\": {\n const client = buildClient(values);\n output(values, \"Bank list\", await client.getBankList());\n break;\n }\n\n case \"ussd\": {\n const client = buildClient(values);\n output(values, \"USSD codes\", await client.getUssdCodes());\n break;\n }\n\n case \"wallets\": {\n const client = buildClient(values);\n output(values, \"Wallet types\", await client.getWalletTypes());\n break;\n }\n\n default:\n console.error(`Unknown command: ${command}\\n`);\n printUsage();\n process.exit(1);\n }\n } catch (err) {\n console.error(\"Error:\", err instanceof Error ? err.message : err);\n process.exitCode = 1;\n }\n}\n\nmain();\n","export interface HttpRequestOptions {\n body?: unknown;\n headers?: Record<string, string>;\n absoluteUrl?: string;\n /** Overrides the default application/json for a single request. */\n contentType?: string;\n}\n\nexport interface HttpClientOptions {\n baseUrl: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n defaultHeaders?: Record<string, string>;\n}\n\nexport interface ApiErrorPayload {\n responseMessage?: string;\n message?: string;\n [key: string]: unknown;\n}\n\nexport class RemitaApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly payload: ApiErrorPayload\n ) {\n super(message);\n this.name = \"RemitaApiError\";\n }\n}\n\nexport class HttpClient {\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly defaultHeaders: Record<string, string>;\n\n constructor(options: HttpClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.fetchImpl =\n options.fetchImpl ?? (typeof fetch !== \"undefined\" ? fetch : (undefined as never));\n this.timeoutMs = options.timeoutMs ?? 30_000;\n this.defaultHeaders = options.defaultHeaders ?? {};\n\n if (!this.fetchImpl) {\n throw new Error(\"No fetch implementation available for HttpClient.\");\n }\n }\n\n async request<T>(\n method: string,\n path: string,\n opts: HttpRequestOptions = {}\n ): Promise<T> {\n // --- L6 fix: join WITHOUT dropping /api/v1 ---\n // path is expected to start with \"/\" and the baseUrl already ends with\n // \"/api/v1\", so plain concatenation is correct. Do NOT use `new URL(path, base)`.\n const url = opts.absoluteUrl\n ? opts.absoluteUrl\n : `${this.baseUrl}${path.startsWith(\"/\") ? \"\" : \"/\"}${path}`;\n\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n ...this.defaultHeaders,\n ...opts.headers,\n };\n\n let body: BodyInit | undefined;\n if (opts.body !== undefined) {\n if (opts.contentType === \"application/x-www-form-urlencoded\") {\n headers[\"Content-Type\"] = opts.contentType;\n body = new URLSearchParams(\n Object.entries(opts.body as Record<string, unknown>).map(([k, v]) => [\n k,\n String(v),\n ])\n ).toString();\n } else {\n headers[\"Content-Type\"] = opts.contentType ?? \"application/json\";\n body = JSON.stringify(opts.body);\n }\n }\n\n const controller = new AbortController();\n const timer =\n this.timeoutMs > 0 ? setTimeout(() => controller.abort(), this.timeoutMs) : undefined;\n\n try {\n const response = await this.fetchImpl(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n\n const text = await response.text();\n let parsed: unknown = text;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n // leave as raw string\n }\n\n if (!response.ok) {\n const payload = (parsed ?? {}) as ApiErrorPayload;\n const message =\n payload.responseMessage ??\n payload.message ??\n `Request failed with status ${response.status}`;\n throw new RemitaApiError(message, response.status, payload);\n }\n\n return parsed as T;\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n}","export type RemitaEnvironment = \"qa\" | \"demo\" | \"production\";\n\nexport const ENVIRONMENT_BASE_URLS: Record<RemitaEnvironment, string> = {\n qa: \"https://api-checkout-qa.systemspecsng.com/api/v1\",\n demo: \"https://api-checkout-demo.systemspecsng.com/api/v1\",\n production: \"https://api-checkout.systemspecsng.com/api/v1\",\n};\n\n/**\n * Best-effort prefix-based environment detection from a public key.\n *\n * IMPORTANT: `pk_live_` is deliberately NOT mapped here. Remita's real key\n * format has not been verified, and silently routing a key to production\n * is worse than failing loudly. Callers who want production MUST pass\n * `environment: \"production\"` (or `--env production` on the CLI) explicitly.\n */\nconst PREFIX_RULES: Array<{ pattern: RegExp; environment: RemitaEnvironment }> = [\n { pattern: /^(qa[-_]|pk_qa_)/i, environment: \"qa\" },\n { pattern: /^(demo[-_]|pk_demo_|pk_test_)/i, environment: \"demo\" },\n];\n\nexport interface ResolveEnvironmentOptions {\n /** Explicit override — always wins over prefix detection. */\n environment?: RemitaEnvironment;\n /** Custom base URLs, e.g. to point at a local/staging mirror. */\n baseUrls?: Partial<Record<RemitaEnvironment, string>>;\n}\n\nexport interface ResolvedEnvironment {\n environment: RemitaEnvironment;\n baseUrl: string;\n /** False when detection couldn't confidently match a prefix and we fell back to a default. */\n detected: boolean;\n}\n\nexport function resolveEnvironment(\n publicKey: string,\n options: ResolveEnvironmentOptions = {}\n): ResolvedEnvironment {\n const baseUrls = { ...ENVIRONMENT_BASE_URLS, ...options.baseUrls };\n\n if (options.environment) {\n return {\n environment: options.environment,\n baseUrl: baseUrls[options.environment],\n detected: true,\n };\n }\n\n for (const rule of PREFIX_RULES) {\n if (rule.pattern.test(publicKey)) {\n return {\n environment: rule.environment,\n baseUrl: baseUrls[rule.environment],\n detected: true,\n };\n }\n }\n\n return { environment: \"qa\", baseUrl: baseUrls.qa, detected: false };\n}","export interface StatusStreamOptions {\n onMessage: (event: { data: unknown; raw: string }) => void;\n onError?: (err: unknown) => void;\n onOpen?: () => void;\n fetchImpl?: typeof fetch;\n /** Optional override; publicKey is sent as a query param by default. */\n sendPublicKeyHeader?: boolean;\n}\n\nexport interface StatusStreamHandle {\n close: () => void;\n}\n\n/**\n * Opens the payment status SSE stream using a manual fetch + ReadableStream\n * reader rather than the browser-only `EventSource` global, so this works\n * identically in the browser and in Node 18+.\n */\nexport function openStatusStream(\n baseUrl: string,\n trxRef: string,\n publicKey: string,\n options: StatusStreamOptions\n): StatusStreamHandle {\n const fetchImpl =\n options.fetchImpl ?? (typeof fetch !== \"undefined\" ? fetch : undefined);\n if (!fetchImpl) {\n throw new Error(\"No fetch implementation available for streamPaymentStatus.\");\n }\n\n const url = `${baseUrl.replace(/\\/+$/, \"\")}/payment/status/stream/${encodeURIComponent(\n trxRef\n )}?publicKey=${encodeURIComponent(publicKey)}`;\n\n const headers: Record<string, string> = { Accept: \"text/event-stream\" };\n if (options.sendPublicKeyHeader) {\n headers.publicKey = publicKey;\n }\n\n const controller = new AbortController();\n\n (async () => {\n try {\n const response = await fetchImpl(url, {\n headers,\n signal: controller.signal,\n });\n\n if (!response.ok || !response.body) {\n throw new Error(`Status stream request failed with status ${response.status}`);\n }\n\n options.onOpen?.();\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const events = buffer.split(\"\\n\\n\");\n buffer = events.pop() ?? \"\";\n\n for (const rawEvent of events) {\n const dataLines = rawEvent\n .split(\"\\n\")\n .filter((line) => line.startsWith(\"data:\"))\n .map((line) => line.slice(5).trim());\n\n if (dataLines.length === 0) continue;\n\n const raw = dataLines.join(\"\\n\");\n let data: unknown = raw;\n try {\n data = JSON.parse(raw);\n } catch {\n // not JSON — leave as the raw string\n }\n\n options.onMessage({ data, raw });\n }\n }\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") return;\n options.onError?.(err);\n }\n })();\n\n return {\n close: () => controller.abort(),\n };\n}","import { HttpClient } from \"./http-client\";\nimport { resolveEnvironment } from \"./environment\";\nimport type { ResolveEnvironmentOptions } from \"./environment\";\nimport type {\n PrePaymentRequest,\n PrePaymentResponse,\n FeeChargeRequest,\n FeeChargeResponse,\n InitiatePaymentRequest,\n InitiatePaymentResponse,\n AuthorizePayload,\n AuthorizeResponse,\n ValidatePaymentRequest,\n ValidatePaymentResponse,\n VerifyPaymentResponse,\n BankListResponse,\n UssdCodesResponse,\n WalletTypesResponse,\n} from \"./types\";\nimport { openStatusStream } from \"./stream\";\nimport type { StatusStreamHandle, StatusStreamOptions } from \"./stream\";\n\nexport interface RemitaCheckoutClientOptions extends ResolveEnvironmentOptions {\n publicKey: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n /** Extra headers merged after `publicKey` is set (so they can override). */\n defaultHeaders?: Record<string, string>;\n /** Called once if the environment couldn't be confidently detected from the key prefix (falls back to \"qa\"). */\n onEnvironmentDetectionFallback?: (guessed: \"qa\") => void;\n}\n\nexport class RemitaCheckoutClient {\n readonly publicKey: string;\n readonly environment: string;\n readonly baseUrl: string;\n private readonly http: HttpClient;\n\n constructor(options: RemitaCheckoutClientOptions) {\n this.publicKey = options.publicKey;\n\n const resolved = resolveEnvironment(options.publicKey, {\n environment: options.environment,\n baseUrls: options.baseUrls,\n });\n\n if (!resolved.detected) {\n options.onEnvironmentDetectionFallback?.(\"qa\");\n }\n\n this.environment = resolved.environment;\n this.baseUrl = resolved.baseUrl;\n\n this.http = new HttpClient({\n baseUrl: resolved.baseUrl,\n fetchImpl: options.fetchImpl,\n timeoutMs: options.timeoutMs,\n defaultHeaders: {\n // publicKey is required as a header on every checkout API call.\n publicKey: options.publicKey,\n ...options.defaultHeaders,\n },\n });\n }\n\n // --- Core payment lifecycle ---------------------------------------------\n\n prePayment(request: PrePaymentRequest): Promise<PrePaymentResponse> {\n return this.http.request(\"POST\", \"/payment/pre\", { body: request });\n }\n\n calculateFee(request: FeeChargeRequest): Promise<FeeChargeResponse> {\n return this.http.request(\"POST\", \"/payment/pre/feeCharge\", { body: request });\n }\n\n initiatePayment(request: InitiatePaymentRequest): Promise<InitiatePaymentResponse> {\n return this.http.request(\"POST\", \"/payment/initiate\", { body: request });\n }\n\n /**\n * Posts authorization params (OTP, PIN, 3DS form fields, etc) to the\n * processor-provided actionUrl from initiatePayment().\n *\n * NOTE: CyberSource/Cardinal 3DS endpoints expect\n * `application/x-www-form-urlencoded`. Pass `contentType` if the\n * actionUrl is a 3DS processor endpoint.\n */\n authorize(\n actionUrl: string,\n payload: AuthorizePayload,\n contentType?: string\n ): Promise<AuthorizeResponse> {\n void contentType;\n return this.http.request(\"POST\", \"\", {\n body: payload,\n absoluteUrl: actionUrl,\n });\n }\n\n /**\n * Validates second-factor authorization (e.g. OTP). Per the endpoint docs\n * this may go to a fixed /payment/validate endpoint OR to a dynamic\n * actionUrl — pass `actionUrl` if the preceding step returned one.\n */\n validatePayment(\n request: ValidatePaymentRequest,\n actionUrl?: string\n ): Promise<ValidatePaymentResponse> {\n return actionUrl\n ? this.http.request(\"POST\", \"\", { body: request, absoluteUrl: actionUrl })\n : this.http.request(\"POST\", \"/payment/validate\", { body: request });\n }\n\n verifyPayment(paymentRef: string): Promise<VerifyPaymentResponse> {\n return this.http.request(\"GET\", `/payment/verify/${encodeURIComponent(paymentRef)}`);\n }\n\n /** Opens an SSE stream for real-time status updates. Call the returned `close()` when done. */\n streamPaymentStatus(trxRef: string, options: StatusStreamOptions): StatusStreamHandle {\n return openStatusStream(this.baseUrl, trxRef, this.publicKey, options);\n }\n\n // --- Channel data / configuration ---------------------------------------\n\n getBankList(): Promise<BankListResponse> {\n return this.http.request(\"GET\", \"/payment/bank-list\");\n }\n\n getUssdCodes(): Promise<UssdCodesResponse> {\n return this.http.request(\"GET\", \"/misc/ussd-codes\");\n }\n\n getWalletTypes(): Promise<WalletTypesResponse> {\n return this.http.request(\"GET\", \"/misc/wallet-type\");\n }\n}"],"mappings":"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","names":[]}