@herberthtk/yo-payments-api 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/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ Releases are managed with [release-it](https://github.com/release-it/release-it)
6
+ using [Conventional Commits](https://www.conventionalcommits.org/).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 herberthtk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # @herberthtk/yo-payments-api
2
+
3
+ TypeScript client for the [Yo! Payments API PHP library](https://github.com/YO-Uganda) (`YoAPI.php`) for mobile money, airtime and account operations on the Yo! Payments gateway. Runs on [Bun](https://bun.com) and Node.js 18+ (uses `fetch` + `node:crypto`), including Next.js App Router handlers, Server Actions and Server Components (**server-side only** — never import it into a Client Component).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @herberthtk/yo-payments-api
9
+ # or: bun add @herberthtk/yo-payments-api
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ import { YoAPI } from "@herberthtk/yo-payments-api";
16
+
17
+ // production by default; pass "sandbox" as the third argument for sandbox mode
18
+ const yoAPI = new YoAPI("API_USERNAME", "API_PASSWORD");
19
+
20
+ // Request a mobile money user to deposit funds into your account
21
+ const response = await yoAPI.acDepositFunds("256770000000", 10000, "Reason for transfer of funds");
22
+ if (response.Status === "OK") {
23
+ console.log("Transaction Reference =", response.TransactionReference);
24
+ }
25
+
26
+ // Check the balance of your account
27
+ const balance = await yoAPI.acAcctBalance();
28
+ console.log(balance.balance); // [{ code: "UGX", balance: "50000" }, ...]
29
+ ```
30
+
31
+ All network methods are `async` and return typed response objects. Method names use idiomatic camelCase (e.g. `acDepositFunds`, `setExternalReference`, `getTransactionLimitAccountIdentifier`) — the one intentional divergence from the PHP library's `snake_case` names; the XML wire format is unchanged.
32
+
33
+ ### Available operations
34
+
35
+ - `acDepositFunds(msisdn, amount, narrative)`
36
+ - `acTransactionCheckStatus(transactionReference, privateTransactionReference?)`
37
+ - `acInternalTransfer(currencyCode, amount, beneficiaryAccount, beneficiaryEmail, narrative)`
38
+ - `acAcctBalance()`
39
+ - `acGetMinistatement(startDate?, endDate?, transactionStatus?, currencyCode?, resultSetLimit?, transactionEntryDesignation?, externalReference?)`
40
+ - `acSendAirtimeMobile(msisdn, amount, narrative)`
41
+ - `acSendAirtimeInternal(currencyCode, amount, beneficiaryAccount, beneficiaryEmail, narrative)`
42
+ - `acWithdrawFunds(msisdn, amount, narrative)`
43
+ - `acUserPurchaseAirtimestock(airtimeCurrencyCode, amount)`
44
+ - `acGetMsisdnKycInfo(msisdn)`
45
+ - `generatePublicKeyAuthenticationSignature(msisdn, amount, narrative)`
46
+
47
+ ### Receiving payment notifications (IPN)
48
+
49
+ PHP reads `$_POST` / `php://input` globals, which is impossible in TypeScript, so you pass the parsed form body yourself. Point `setUrl`-style config is not affected; use `setPublicKeyFileUrl` if you need a different certificate (sandbox vs production is picked automatically by the constructor `mode`).
50
+
51
+ ```ts
52
+ // Bun HTTP server example
53
+ Bun.serve({
54
+ port: 3000,
55
+ async fetch(req) {
56
+ const form = await req.formData();
57
+ const body = Object.fromEntries(form.entries()) as any;
58
+
59
+ const yoAPI = new YoAPI("API_USERNAME", "API_PASSWORD", "sandbox");
60
+ const payment = yoAPI.receivePaymentNotification(body);
61
+ if (payment.is_verified) {
62
+ console.log(`Payment from ${payment.msisdn} of ${payment.amount} (ref ${payment.external_ref})`);
63
+ // update your transaction status where external_ref = payment.external_ref
64
+ }
65
+
66
+ // Failure notifications:
67
+ // const failure = yoAPI.receivePaymentFailureNotification(body);
68
+ return new Response("OK");
69
+ },
70
+ });
71
+ ```
72
+
73
+ ### Public key authentication (payouts)
74
+
75
+ ```ts
76
+ const yoAPI = new YoAPI("API_USERNAME", "API_PASSWORD");
77
+ yoAPI.setExternalReference("INV-123");
78
+ yoAPI.setPublicKeyAuthenticationNonce(crypto.randomUUID());
79
+ yoAPI.setPrivateKeyFileLocation("/path/to/your-private-key.pem");
80
+ yoAPI.generatePublicKeyAuthenticationSignature("256770000000", 5000, "Salary payout");
81
+ const res = await yoAPI.acWithdrawFunds("256770000000", 5000, "Salary payout");
82
+ ```
83
+
84
+ ### Usage in Next.js (App Router)
85
+
86
+ The library is **server-only**: it uses `node:crypto`/`node:fs` and handles API secrets. Add `import "server-only"` (`npm i server-only`) at the top of every file that touches it, keep credentials in server-side env vars (never `NEXT_PUBLIC_*`), and pin `export const runtime = "nodejs"` on route handlers. Ready-to-copy handlers live in `examples/nextjs/`:
87
+
88
+ ```bash
89
+ npm install @herberthtk/yo-payments-api server-only
90
+ ```
91
+
92
+ ```ts
93
+ // lib/yo.ts
94
+ import "server-only";
95
+ import { YoAPI } from "@herberthtk/yo-payments-api";
96
+
97
+ export function getYoClient() {
98
+ return new YoAPI(process.env.YO_API_USERNAME!, process.env.YO_API_PASSWORD!, "sandbox");
99
+ }
100
+ ```
101
+
102
+ ```ts
103
+ // app/api/yo/ipn/route.ts — register this URL as your InstantNotificationUrl
104
+ import "server-only";
105
+ import { getYoClient } from "@/lib/yo";
106
+
107
+ export const runtime = "nodejs";
108
+ export const dynamic = "force-dynamic";
109
+
110
+ export async function POST(req: Request) {
111
+ const form = await req.formData();
112
+ const body: Record<string, string> = {};
113
+ for (const [k, v] of form.entries()) if (typeof v === "string") body[k] = v;
114
+
115
+ const payment = getYoClient().receivePaymentNotification({
116
+ date_time: body.date_time ?? "",
117
+ amount: body.amount ?? "",
118
+ narrative: body.narrative ?? "",
119
+ network_ref: body.network_ref ?? "",
120
+ external_ref: body.external_ref ?? "",
121
+ msisdn: body.msisdn ?? "",
122
+ signature: body.signature ?? "",
123
+ });
124
+ if (!payment.is_verified) return new Response("NOT VERIFIED", { status: 400 });
125
+
126
+ // TODO: persist + mark processed idempotently on payment.external_ref
127
+ return new Response("OK");
128
+ }
129
+ ```
130
+
131
+ ```ts
132
+ // app/actions.ts — deposits from a Client Component form action
133
+ "use server";
134
+ import { getYoClient } from "@/lib/yo";
135
+
136
+ export async function requestDeposit(msisdn: string, amount: number, narrative: string) {
137
+ const api = getYoClient();
138
+ api.setExternalReference(`${Date.now()}`);
139
+ const res = await api.acDepositFunds(msisdn, amount, narrative);
140
+ if (res.Status === "OK") return { ok: true, reference: res.TransactionReference };
141
+ return { ok: false, message: res.StatusMessage };
142
+ }
143
+ ```
144
+
145
+ ```tsx
146
+ // app/statement/page.tsx — Server Component (all reads return JSON-safe data)
147
+ import { getYoClient } from "@/lib/yo";
148
+
149
+ export default async function StatementPage() {
150
+ const res = await getYoClient().acGetMinistatement(null, null, "SUCCEEDED", "UGX-MTNMM", 0);
151
+ return <pre>{JSON.stringify(res.Transactions, null, 2)}</pre>;
152
+ }
153
+ ```
154
+
155
+ Feature map: deposits/status checks → Server Actions (`examples/nextjs/lib/actions.ts`); balances/ministatements/KYC → Server Components or actions (`examples/nextjs/lib/queries.ts`); IPN + failure notices → Route Handlers (`examples/nextjs/app/api/yo/...`); payouts → Server Actions with `setPrivateKeyContent(process.env.YO_PRIVATE_KEY!.replace(/\\n/g, "\n"))` since serverless hosts have no key files. See `examples/nextjs/` for every operation, covered by `tests/nextjs.test.ts`.
156
+
157
+ ### Error handling
158
+
159
+ Transport-level and protocol-level failures throw `YoAPIError` (an `Error` subclass):
160
+
161
+ ```ts
162
+ import { YoAPI, YoAPIError } from "@herberthtk/yo-payments-api";
163
+
164
+ try {
165
+ await yoAPI.acAcctBalance();
166
+ } catch (e) {
167
+ if (e instanceof YoAPIError) {
168
+ console.error(e.message, "status:", e.status, "cause:", e.cause);
169
+ }
170
+ }
171
+ ```
172
+
173
+ `YoAPIError` is thrown for connection errors, timeouts, non-2xx HTTP statuses, oversized bodies, malformed XML and responses missing the `<Response>` node. Gateway-level business failures (e.g. `Status: "FAILED"`) are still returned as normal response objects, exactly like the PHP library.
174
+
175
+ ### Examples
176
+
177
+ The `examples/` directory ports all six PHP examples; each exports testable functions and is runnable with `bun run`:
178
+
179
+ ```bash
180
+ YO_API_USERNAME=... YO_API_PASSWORD=... YO_API_MODE=sandbox bun run examples/deposit_funds.ts
181
+ YO_API_USERNAME=... YO_API_PASSWORD=... YO_API_MODE=sandbox bun run examples/deposit_funds_nonblocking.ts
182
+ YO_API_USERNAME=... YO_API_PASSWORD=... YO_API_MODE=sandbox bun run examples/get_ministatement.ts
183
+ YO_API_USERNAME=... YO_API_PASSWORD=... YO_API_MODE=sandbox \
184
+ YO_PRIVATE_KEY_FILE=/path/to/private-key.pem \
185
+ bun run examples/withdraw_funds_public_key_authentication.ts
186
+ ```
187
+
188
+ `examples/receive_payment_notification.ts` and `examples/receive_payment_failure_notification.ts` export `handlePaymentNotification` / `handlePaymentFailureNotification` for wiring into your HTTP server. All examples are covered by `tests/examples.test.ts`.
189
+
190
+ ## Notes on parity with the PHP library
191
+
192
+ - Request XML element order and optional-element inclusion rules match the PHP library exactly (values are inserted verbatim — escape special XML characters yourself, as with the PHP version). Empty-string and `"0"` response fields follow PHP's `empty()` semantics.
193
+ - `acUserPurchaseAirtimestock` sends `external_reference` inside a `<TransactionReference>` tag, exactly like the PHP code.
194
+ - The PHP library's private `deposit_transaction_type` (used by `acTransactionCheckStatus`) has no setter in PHP and is stuck on `"PULL"`; this port adds `setDepositTransactionType` / `getDepositTransactionType` so `"PUSH"` is usable.
195
+ - A `ResultSetLimit` of `0` is sent to the gateway (returns all, per gateway docs). The PHP example passes `0` but its `!= NULL` check silently drops it; this port sends it.
196
+ - Timeouts default to 120 s (`setTimeout` / `getTimeout` can change it); a timeout `<= 0` disables the timeout, mirroring PHP curl semantics.
197
+ - **TLS verification differs from PHP on purpose:** the PHP library disables peer verification, but this port verifies the gateway certificate by default. Opt out only for testing via `setTlsVerificationEnabled(false)` (on Node.js this additionally requires `NODE_TLS_REJECT_UNAUTHORIZED=0`).
198
+ - Response bodies are capped (`setMaxResponseBytes` / `getMaxResponseBytes`, default 1 MiB) and malformed/non-XML responses throw `YoAPIError` instead of degrading to empty results.
199
+ - Pass money amounts as strings when exact formatting matters; numbers use JavaScript float-to-string conversion.
200
+ - One `YoAPI` instance holds per-request state (`externalReference`, ...), so don't share an instance across concurrent requests — create one per request.
201
+ - The Yo! Uganda public certificates (`certs/*.crt`, copied from the PHP package) verify IPN signatures, with embedded copies as fallback when the files can't be resolved (bundled servers, CJS builds). Override with `setPublicKeyFileUrl`. Verification is fail-closed (`is_verified: false`) when the certificate is missing or invalid — monitor this, and handle IPNs idempotently on `external_ref` since notifications carry no replay protection.
202
+ - `setPrivateKeyContent` accepts the signing key as PEM text (takes precedence over the file location) for hosts without a stable filesystem; on serverless, load it from an env var and unescape newlines.
203
+
204
+ ## Develop
205
+
206
+ ```bash
207
+ bun install
208
+ bun test # mock gateway server + generated RSA keys; no real API calls
209
+ bun run typecheck # tsc --noEmit
210
+ bun run build # tsup → dist/ (ESM + CJS + .d.ts); regenerates src/embeddedCerts.ts first
211
+ bunx attw --pack # validate the packed types
212
+ ```
213
+
214
+ The suite (`tests/YoAPI.test.ts`, `tests/examples.test.ts`, `tests/keys.test.ts`, `tests/nextjs.test.ts`) asserts byte-exact request XML for every operation, response parsing, signature round-trips, key/cert handling and all examples. `tsc --noEmit` must also pass.
215
+
216
+ ### Releasing (maintainers)
217
+
218
+ Versions follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:` → minor, `fix:` → patch, `feat!:`/`BREAKING CHANGE:` → major). To cut a release, run **Actions → Release → Run workflow** — release-it bumps the version, updates `CHANGELOG.md`, tags, creates the GitHub release and publishes to npm via trusted publishing (no npm token needed). First-time setup only: `npm login` + one manual `npm publish --access public`, then register the repo as a trusted publisher in the npm package settings.
219
+
220
+ ## Project structure
221
+
222
+ - `src/YoAPI.ts` — the `YoAPI` client class (public API: config, operations, notifications, signing)
223
+ - `src/types.ts` — public response/body TypeScript interfaces
224
+ - `src/errors.ts` — `YoAPIError`
225
+ - `src/xml.ts` — request building, response parsing and PHP-parity mapping helpers
226
+ - `src/http.ts` — gateway POST transport (timeout, TLS, size cap, error mapping)
227
+ - `src/keys.ts` — cached verification-key loading (file-first, embedded fallback)
228
+ - `src/constants.ts` — gateway URLs, certificate names, defaults
229
+ - `src/embeddedCerts.ts` — auto-generated from `certs/` (`bun run embed-certs`)
230
+ - `examples/` — runnable ports of the six PHP examples
231
+ - `examples/nextjs/` — Next.js App Router handlers, Server Actions and queries
232
+ - `certs/` — Yo! Uganda public certificates for IPN verification
233
+ - `.github/workflows/` — `ci.yml` (test/typecheck/build/pack-check) and `release.yml` (release-it via trusted publishing)
234
+ - `scripts/embed-certs.ts` — regenerates `src/embeddedCerts.ts`
235
+
236
+ This project was created using `bun init` in bun v1.4.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
@@ -0,0 +1,28 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEvTCCA6WgAwIBAgIJAN3e7VqDg5zQMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYD
3
+ VQQGEwJVRzEQMA4GA1UECBMHS2FtcGFsYTEQMA4GA1UEBxMHS2FtcGFsYTEbMBkG
4
+ A1UECgwSWW8hIFVnYW5kYSBMaW1pdGVkMRUwEwYDVQQLDAxZbyEgUGF5bWVudHMx
5
+ FTATBgNVBAMTDHd3dy55by5jby51ZzEcMBoGCSqGSIb3DQEJARYNaW5mb0B5by5j
6
+ by51ZzAeFw0xMzA4MDkwNTQyMTRaFw0yMzA4MDcwNTQyMTRaMIGaMQswCQYDVQQG
7
+ EwJVRzEQMA4GA1UECBMHS2FtcGFsYTEQMA4GA1UEBxMHS2FtcGFsYTEbMBkGA1UE
8
+ CgwSWW8hIFVnYW5kYSBMaW1pdGVkMRUwEwYDVQQLDAxZbyEgUGF5bWVudHMxFTAT
9
+ BgNVBAMTDHd3dy55by5jby51ZzEcMBoGCSqGSIb3DQEJARYNaW5mb0B5by5jby51
10
+ ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPPo+N67Z56ebScXJ9tX
11
+ tFpSNBNNyDlqU/X8bqouZjWuvxpWOI4xZkPKXi0t205ooVbQL/+962NASjJRrouQ
12
+ IUhJq7xhwb+KKcWyFpA25742mNgaxeZJa9iofiHeKotBvHz6pswuqa2gXAyTTmYf
13
+ j6BOIFhDeUffOjfJYbzACy7WLtbK6VIRSTHypQY+zMQluw1euyY8524GYzf8E+c5
14
+ 9qjIa5YY5PPianvvR25VDNRCm0Z6GPolhIGvYPUWHFZx+HtU8xoZumi5Kddvipew
15
+ uujxNVBRyQ8bVRoYxKKuDMFHiXA6V01oPzSOtfPK7JI+rd2JFU7dQgbFxTXI9+Qx
16
+ 2yUCAwEAAaOCAQIwgf8wHQYDVR0OBBYEFPj0nwwE8lJByx243yV6cfXbTKbhMIHP
17
+ BgNVHSMEgccwgcSAFPj0nwwE8lJByx243yV6cfXbTKbhoYGgpIGdMIGaMQswCQYD
18
+ VQQGEwJVRzEQMA4GA1UECBMHS2FtcGFsYTEQMA4GA1UEBxMHS2FtcGFsYTEbMBkG
19
+ A1UECgwSWW8hIFVnYW5kYSBMaW1pdGVkMRUwEwYDVQQLDAxZbyEgUGF5bWVudHMx
20
+ FTATBgNVBAMTDHd3dy55by5jby51ZzEcMBoGCSqGSIb3DQEJARYNaW5mb0B5by5j
21
+ by51Z4IJAN3e7VqDg5zQMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
22
+ AGCaUMHBxGVtVsA8xMDWknjH6hV9yuca3s0qRrOoMfM7nyOjeYtUNgZlsLxuX2n3
23
+ FhoeK9DUBvIKVSlVfO5SXgsXyWKG54YFEkZ8D50Krsyl5NCfaAJezkQ0MNdtpG98
24
+ wlD/cYa6C6DC/s1eilUbI5QqaxLo+EFy5VuHQ8tAuxJbNTVPMW9GvTjxofeMUnug
25
+ SxUMDqHmEkzbQV7yCBVqf3yi4XOM4/6B7Tr6gaandpuR+v2XaKl4SOf8G5svn96g
26
+ Kn+Bk8p6rlBWAl+5hWxHWi4dkjiLsk8q+aeKh6ibwYtRjEt/sbWTgJAZjI1mTT8d
27
+ wsLYlL7k1O3wCjUeMQzi274=
28
+ -----END CERTIFICATE-----
@@ -0,0 +1,35 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ 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
35
+ -----END CERTIFICATE-----