remita-checkout-client 0.1.0 → 0.1.2

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 CHANGED
@@ -1,30 +1,21 @@
1
1
  # remita-checkout-client
2
2
 
3
- A typed client for the Remita Checkout REST API (`api-checkout*.systemspecsng.com`).
4
- Works in the browser and in Node 18+ (both have global `fetch`).
3
+ A typed TypeScript client for the Remita Checkout API. It lets you accept
4
+ payments, run the full payment lifecycle, check payment status in real time, and
5
+ look up supported channels — from the browser or Node.js 18+.
5
6
 
6
- ## ⚠️ Before you use this in anything real
7
+ Two clients are available:
7
8
 
8
- The request/response **field names** in `src/types.ts` were written from
9
- endpoint paths + one-line descriptions only — not the actual DTOs. Every
10
- interface has an index signature so unexpected fields won't break at the
11
- type level, but treat the field names themselves as **unverified**. To
12
- tighten them, pull the actual interfaces out of:
9
+ - **`MerchantCheckoutClient`** — start a checkout and verify the result. Best for
10
+ storefronts and businesses that only need to accept payment for a sale.
11
+ - **`RemitaPlatformClient`** — the full payment lifecycle (pre-payment, fees,
12
+ initiation, authorization, validation, verification) plus channel lookups.
13
+ Best for payment facilitators, gateways, and platforms.
13
14
 
14
- - `remita-api-client.service.ts`
15
- - `payment-status.service.ts`
15
+ ## Requirements
16
16
 
17
- and update `src/types.ts` to match. The transport layer, URLs, HTTP
18
- methods, and the pre → fee → initiate → authorize → validate → verify
19
- sequencing are all solid; it's specifically the shape of each request/
20
- response body that needs confirming.
21
-
22
- The **environment auto-detection from key prefix** (`src/environment.ts`)
23
- is also a guess (Stripe-style `pk_test_`/`pk_live_` conventions) — it has
24
- not been verified against Remita's real key format. It intentionally never
25
- silently resolves to `"production"` — an unrecognized prefix falls back to
26
- `"qa"` and reports `detected: false` so you can log/warn on it. Until you
27
- verify the real prefixes, pass `environment` explicitly and skip detection.
17
+ - Node.js 18 or newer, or a browser with `fetch` support.
18
+ - A Remita public key for your environment.
28
19
 
29
20
  ## Install
30
21
 
@@ -32,53 +23,178 @@ verify the real prefixes, pass `environment` explicitly and skip detection.
32
23
  npm install remita-checkout-client
33
24
  ```
34
25
 
35
- ## Usage
26
+ The package includes TypeScript types and works with both ESM and CommonJS.
27
+
28
+ ## Environments
29
+
30
+ Choose the environment that matches your key:
31
+
32
+ | Environment | Base URL |
33
+ | ------------ | ----------------------------------------------------- |
34
+ | `qa` | `https://api-checkout-qa.systemspecsng.com/api/v1` |
35
+ | `demo` | `https://api-checkout-demo.systemspecsng.com/api/v1` |
36
+ | `production` | `https://api-checkout.systemspecsng.com/api/v1` |
37
+
38
+ Set the environment explicitly, especially for production.
39
+
40
+ ```ts
41
+ import { RemitaPlatformClient } from "remita-checkout-client";
42
+
43
+ const client = new RemitaPlatformClient({
44
+ publicKey: process.env.REMITA_PUBLIC_KEY!,
45
+ environment: "qa",
46
+ });
47
+ ```
48
+
49
+ Your public key is sent in a request header. Keep secret keys out of browser
50
+ code, and always verify a payment on your server before fulfilling an order.
36
51
 
37
- ### Low-level client
52
+ ## Merchant checkout
53
+
54
+ Merchant payments use Remita's hosted inline checkout. The client loads Remita's
55
+ `payment-checkout.js`, then drives the global `RmPaymentEngine` — the same
56
+ integration as Remita's own inline sample — and can verify the payment
57
+ afterwards over REST.
38
58
 
39
59
  ```ts
40
- import { RemitaCheckoutClient } from "remita-checkout-client";
60
+ import { MerchantCheckoutClient } from "remita-checkout-client";
61
+
62
+ const checkout = new MerchantCheckoutClient({
63
+ publicKey: "MERCHANT_PUBLIC_KEY",
64
+ environment: "production",
65
+ });
41
66
 
42
- const client = new RemitaCheckoutClient({
43
- publicKey: "YOUR_PUBLIC_KEY",
44
- // environment: "qa", // recommended explicitly until prefix detection is verified
45
- onEnvironmentDetectionFallback: (guessed) =>
46
- console.warn(`Couldn't detect environment from key prefix, defaulting to ${guessed}`),
67
+ // Loads Remita's checkout script once and calls RmPaymentEngine.init.
68
+ const engine = await checkout.init({
69
+ paymentIdentifier: 788885554,
70
+ customerId: "abc@ya.com",
71
+ firstName: "Mike",
72
+ lastName: "Mike",
73
+ email: "abc@ya.com",
74
+ phoneNumber: "08067493797",
75
+ amount: "3000",
76
+ narration: "Test Payment",
77
+ currency: "NGN",
78
+ // channels: "TRANSFER",
79
+ onSuccess: (response) => console.log("success", response),
80
+ onError: (response) => console.log("error", response),
81
+ onClose: () => console.log("closed"),
47
82
  });
48
83
 
49
- const pre = await client.prePayment({ publicKey: client.publicKey, rrr: "RRR12345" });
50
- const fee = await client.calculateFee({ publicKey: client.publicKey, amount: 5000, channel: "card" });
51
- const initiated = await client.initiatePayment({ publicKey: client.publicKey, amount: 5000, channel: "card" });
84
+ engine.openIframe();
85
+ ```
52
86
 
87
+ After the customer completes payment, verify it on your server before
88
+ fulfilling the order:
89
+
90
+ ```ts
91
+ const verified = await checkout.verifyPayment("PAYMENT_REF_FROM_REMITA");
92
+ if (verified.status === "APPROVED") {
93
+ // Confirm the order on your backend before fulfilling it.
94
+ }
95
+ ```
96
+
97
+ To collect an existing Remita Retrieval Reference (RRR) instead, use
98
+ `processRRR`, which mirrors `RmPaymentEngine.processRRR`:
99
+
100
+ ```ts
101
+ const engine = await checkout.processRRR({
102
+ rrr: "927227066514",
103
+ paymentIdentifier: "7888855540094893435734834935035408",
104
+ channels: "",
105
+ onSuccess: (response) => console.log("success", response),
106
+ onError: (response) => console.log("error", response),
107
+ onClose: () => console.log("closed"),
108
+ });
109
+
110
+ engine.openIframe();
111
+ ```
112
+
113
+ The hosted checkout is browser-only. `init`, `createCheckoutSession`,
114
+ `createSession`, and `processRRR` return the `RmPaymentEngine` instance, so you
115
+ call `openIframe()` to display it exactly like the script tag example. The
116
+ script URL is chosen from the environment and can be overridden with
117
+ `scriptUrl`.
118
+
119
+ ## Platform lifecycle
120
+
121
+ `RemitaPlatformClient` walks the full flow: pre-payment, fee, initiation,
122
+ authorization, validation, and verification.
123
+
124
+ ```ts
125
+ import { RemitaPlatformClient } from "remita-checkout-client";
126
+
127
+ const remita = new RemitaPlatformClient({
128
+ publicKey: "PSP_PUBLIC_KEY",
129
+ environment: "production",
130
+ });
131
+
132
+ const pre = await remita.prePayment({
133
+ firstName: "Ada",
134
+ lastName: "Okafor",
135
+ email: "ada@example.com",
136
+ phoneNumber: "08000000000",
137
+ paymentIdentifier: "ORDER-123",
138
+ currency: "NGN",
139
+ amount: 25000,
140
+ });
141
+
142
+ const fee = await remita.calculateFee({ amount: 25000, channel: "card" });
143
+
144
+ const paymentIdentifier = pre.transactionId ?? pre.paymentReference;
145
+ if (!paymentIdentifier) throw new Error("Pre-payment did not return a transaction reference");
146
+
147
+ const initiated = await remita.initiatePayment({
148
+ amount: 25000,
149
+ paymentIdentifier,
150
+ paymentChannel: "CARD",
151
+ });
152
+
153
+ // Only when the payment processor asks for it:
53
154
  if (initiated.actionUrl) {
54
- await client.authorize(initiated.actionUrl, { /* OTP / PIN / 3DS fields */ });
155
+ await remita.authorize(initiated.actionUrl, { otp: "123456" });
156
+ await remita.validatePayment({ paymentReference: initiated.paymentReference, otp: "123456" });
55
157
  }
56
158
 
57
- const verified = await client.verifyPayment(initiated.paymentReference!);
159
+ const verified = await remita.verifyPayment(initiated.paymentReference!);
58
160
  console.log(verified.status); // "APPROVED" | "PENDING" | "FAILED" | ...
59
161
  ```
60
162
 
61
- ### Higher-level session (tracks refs between steps for you)
163
+ ### Tracking a payment with PaymentSession
164
+
165
+ `PaymentSession` carries the references between steps so you don't have to, and
166
+ tracks the current stage of the flow. Its `stage` becomes `verified` only when
167
+ the payment is `APPROVED` and `failed` when it is `FAILED`; a `PENDING` payment
168
+ leaves the stage unchanged.
62
169
 
63
170
  ```ts
64
- import { RemitaCheckoutClient, PaymentSession } from "remita-checkout-client";
171
+ import { RemitaPlatformClient, PaymentSession } from "remita-checkout-client";
65
172
 
66
- const client = new RemitaCheckoutClient({ publicKey: "YOUR_PUBLIC_KEY", environment: "qa" });
173
+ const client = new RemitaPlatformClient({ publicKey: "YOUR_PUBLIC_KEY", environment: "qa" });
67
174
  const session = new PaymentSession(client);
68
175
 
69
- await session.pre({ publicKey: client.publicKey, rrr: "RRR12345" });
70
- await session.fee({ publicKey: client.publicKey, amount: 5000, channel: "card" });
71
- await session.initiate({ publicKey: client.publicKey, amount: 5000, channel: "card" });
176
+ await session.pre({
177
+ firstName: "Ada", lastName: "Okafor", email: "ada@example.com",
178
+ phoneNumber: "08000000000", paymentIdentifier: "ORDER-123",
179
+ currency: "NGN", amount: 25000,
180
+ });
181
+ await session.fee({ amount: 25000, channel: "card" });
182
+
183
+ const paymentIdentifier = session.transactionId ?? session.paymentReference;
184
+ if (!paymentIdentifier) throw new Error("Pre-payment did not return a transaction reference");
185
+ await session.initiate({ amount: 25000, paymentIdentifier, paymentChannel: "CARD" });
72
186
 
73
- // actionUrl from initiate() is remembered automatically
187
+ // The actionUrl from initiate() is remembered automatically.
74
188
  await session.authorize({ otp: "123456" });
75
189
  await session.validate({ otp: "123456" });
76
190
 
77
- const result = await session.verify(); // uses paymentReference captured from initiate()
191
+ const result = await session.verify();
78
192
  console.log(session.stage, result.status);
79
193
  ```
80
194
 
81
- ### Real-time status via SSE
195
+ ## Real-time payment status
196
+
197
+ Follow a payment's status live with server-sent events:
82
198
 
83
199
  ```ts
84
200
  const handle = client.streamPaymentStatus(trxRef, {
@@ -87,15 +203,10 @@ const handle = client.streamPaymentStatus(trxRef, {
87
203
  onError: (err) => console.error("stream error:", err),
88
204
  });
89
205
 
90
- // later
91
- handle.close();
206
+ handle.close(); // when you're done
92
207
  ```
93
208
 
94
- This is implemented as a manual `fetch` + `ReadableStream` reader (not the
95
- browser-only `EventSource` global), so it behaves the same in the browser
96
- and in Node.
97
-
98
- ### Channel config endpoints
209
+ ## Channel and configuration lookups
99
210
 
100
211
  ```ts
101
212
  const banks = await client.getBankList();
@@ -103,36 +214,105 @@ const ussdCodes = await client.getUssdCodes();
103
214
  const wallets = await client.getWalletTypes();
104
215
  ```
105
216
 
106
- ## Node < 18
107
-
108
- Global `fetch` isn't available before Node 18. Pass a `fetchImpl` explicitly:
217
+ ## Command line
109
218
 
110
- ```ts
111
- import fetch from "node-fetch";
219
+ The package also installs a `remita-checkout` command with two groups:
220
+ `merchant` and `platform`.
112
221
 
113
- const client = new RemitaCheckoutClient({
114
- publicKey: "...",
115
- environment: "qa",
116
- fetchImpl: fetch as unknown as typeof globalThis.fetch,
117
- });
222
+ ```bash
223
+ remita-checkout <group> <command> [options]
118
224
  ```
119
225
 
120
- ## Not covered in this version
226
+ ### Merchant commands
227
+
228
+ | Command | Description |
229
+ | ------------------ | -------------------------------- |
230
+ | `merchant verify` | Verify a payment by reference |
231
+ | `merchant status` | Alias for `verify` |
232
+
233
+ Merchant checkout itself runs in the browser through the hosted
234
+ `RmPaymentEngine`, so there is no CLI command to start it — the CLI only checks
235
+ payment status.
236
+
237
+ ### Platform commands
238
+
239
+ | Command | Description |
240
+ | -------------------- | --------------------------------------------- |
241
+ | `platform pre` | Pre-payment initialization |
242
+ | `platform fee` | Calculate the fee for a channel |
243
+ | `platform initiate` | Initiate a payment |
244
+ | `platform authorize` | Submit authorization details |
245
+ | `platform validate` | Complete second-factor authorization (OTP) |
246
+ | `platform verify` | Verify payment status by reference |
247
+ | `platform stream` | Stream real-time status updates |
248
+ | `platform banks` | List supported banks |
249
+ | `platform ussd` | List USSD codes |
250
+ | `platform wallets` | List wallet types |
251
+
252
+ ### Options
253
+
254
+ | Option | Description |
255
+ | ----------------------------- | --------------------------------------------------- |
256
+ | `--key <publicKey>` | Remita public key (required for most commands) |
257
+ | `--env <qa\|demo\|production>`| Environment to use |
258
+ | `--payment-identifier <ref>` | Reference from the payment flow |
259
+ | `--payment-channel <channel>` | Payment channel, e.g. `CARD` |
260
+ | `--payload <json>` | Additional details as JSON |
261
+ | `--show-payload` | Preview the request with sensitive fields redacted |
262
+ | `--json` | Print raw JSON, useful for piping |
263
+
264
+ ### Examples
121
265
 
122
- Per the agreed v1 scope, this does **not** include:
266
+ ```bash
267
+ remita-checkout merchant verify --key QA-abc123 --ref PAY-REF-1 --json
268
+ remita-checkout platform pre --key QA-abc123 --rrr 123456789
269
+ remita-checkout platform verify --key QA-abc123 --ref PAY-REF-1 --json
270
+ ```
123
271
 
124
- - Google Pay / Apple Pay config endpoints (`/misc/googlepay`, `/misc/applepay`)
125
- - CyberSource / Cardinal 3DS flow (Cruise Collect iframe, Centinel API, the
126
- return listener endpoint)
272
+ Only use Remita-approved test data in QA. Never pass real card details on the
273
+ command line — `--show-payload` redacts them, but shell history does not.
127
274
 
128
- Both are separate, non-trivial integrations (iframe orchestration for DDC,
129
- CyberSource's Accept.js script loading) — worth their own follow-up rather
130
- than bolting on here.
275
+ ## Errors
131
276
 
132
- ## Development
277
+ Failed requests throw a `RemitaApiError` that includes the HTTP status and the
278
+ error details returned by the API:
133
279
 
134
- ```bash
135
- npm install
136
- npm run typecheck
137
- npm run build # outputs dist/ as CJS + ESM + .d.ts
280
+ ```ts
281
+ import { RemitaApiError } from "remita-checkout-client";
282
+
283
+ try {
284
+ await client.verifyPayment("PAYMENT_REF");
285
+ } catch (error) {
286
+ if (error instanceof RemitaApiError) {
287
+ console.error(error.status, error.message, error.payload);
288
+ } else {
289
+ throw error;
290
+ }
291
+ }
138
292
  ```
293
+
294
+ ## API coverage
295
+
296
+ | Operation | Method and path |
297
+ | ---------------------------- | --------------------------------------------------- |
298
+ | Pre-payment | `POST /payment/pre` |
299
+ | Fee calculation | `POST /payment/pre/feeCharge` |
300
+ | Initiate / merchant checkout | `POST /payment/initiate` |
301
+ | Authorize | `POST` to the action URL returned by initiation |
302
+ | Validate payment | `POST /payment/validate` |
303
+ | Verify payment | `GET /payment/verify/{reference}` |
304
+ | Payment status stream | `GET /payment/status/stream/{transactionReference}` |
305
+ | Bank list | `GET /payment/bank-list` |
306
+ | USSD codes | `GET /misc/ussd-codes` |
307
+ | Wallet types | `GET /misc/wallet-type` |
308
+
309
+ ## Notes
310
+
311
+ - Google Pay / Apple Pay configuration and the CyberSource / Cardinal 3DS iframe
312
+ flow are not included in this version.
313
+ - Confirm request and response details against Remita's current API
314
+ documentation for your environment before going live.
315
+
316
+ ## License
317
+
318
+ MIT