@herberthtk/yo-payments-api 0.2.0 → 0.3.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 +146 -89
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,9 +22,13 @@ import { YoAPI } from "@herberthtk/yo-payments-api";
|
|
|
22
22
|
const yoAPI = new YoAPI("API_USERNAME", "API_PASSWORD");
|
|
23
23
|
|
|
24
24
|
// Request a mobile money user to deposit funds into your account
|
|
25
|
-
const response = await yoAPI.acDepositFunds(
|
|
25
|
+
const response = await yoAPI.acDepositFunds(
|
|
26
|
+
"256770000000",
|
|
27
|
+
10000,
|
|
28
|
+
"Reason for transfer of funds",
|
|
29
|
+
);
|
|
26
30
|
if (response.Status === "OK") {
|
|
27
|
-
|
|
31
|
+
console.log("Transaction Reference =", response.TransactionReference);
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
// Check the balance of your account
|
|
@@ -40,26 +44,26 @@ All network methods are `async` and return typed response objects. Method names
|
|
|
40
44
|
const yoAPI = new YoAPI(username: string, password: string, mode: "production" | "sandbox" = "production");
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
| Setter
|
|
44
|
-
|
|
45
|
-
| `setExternalReference`
|
|
46
|
-
| `setInternalReference`
|
|
47
|
-
| `setNonblocking`
|
|
48
|
-
| `setInstantNotificationUrl`
|
|
49
|
-
| `setFailureNotificationUrl`
|
|
50
|
-
| `setProviderReferenceText`
|
|
51
|
-
| `setAuthenticationSignatureBase64`
|
|
52
|
-
| `setDepositTransactionType`
|
|
53
|
-
| `setTransactionLimitAccountIdentifier`
|
|
54
|
-
| `setPublicKeyAuthenticationNonce`
|
|
55
|
-
| `setPublicKeyAuthenticationSignatureBase64` | `string \| null`
|
|
56
|
-
| `setPrivateKeyFileLocation`
|
|
57
|
-
| `setPrivateKeyContent`
|
|
58
|
-
| `setPublicKeyFileUrl`
|
|
59
|
-
| `setUrl`
|
|
60
|
-
| `setTimeout`
|
|
61
|
-
| `setTlsVerificationEnabled`
|
|
62
|
-
| `setMaxResponseBytes`
|
|
47
|
+
| Setter | Type | Default | Purpose |
|
|
48
|
+
| ------------------------------------------- | ------------------- | ------------ | ------------------------------------------------------------------------------ |
|
|
49
|
+
| `setExternalReference` | `string \| null` | `null` | Your reference for the payment (e.g. invoice number); sent with most requests |
|
|
50
|
+
| `setInternalReference` | `string \| null` | `null` | Reference to another Yo! Payments system transaction |
|
|
51
|
+
| `setNonblocking` | `"TRUE" \| "FALSE"` | `"FALSE"` | `"TRUE"` returns immediately; poll status or use IPN URLs |
|
|
52
|
+
| `setInstantNotificationUrl` | `string \| null` | `null` | URL POSTed on successful deposit (non-blocking flow) |
|
|
53
|
+
| `setFailureNotificationUrl` | `string \| null` | `null` | URL POSTed on failed deposit (non-blocking flow) |
|
|
54
|
+
| `setProviderReferenceText` | `string \| null` | `null` | Text appended to the subscriber's confirmation SMS |
|
|
55
|
+
| `setAuthenticationSignatureBase64` | `string \| null` | `null` | Required for certain deposit requests (ask Yo! support) |
|
|
56
|
+
| `setDepositTransactionType` | `"PULL" \| "PUSH"` | `"PULL"` | Which deposit flow `acTransactionCheckStatus` follows up on |
|
|
57
|
+
| `setTransactionLimitAccountIdentifier` | `string \| null` | `null` | Ask your account administrator before using |
|
|
58
|
+
| `setPublicKeyAuthenticationNonce` | `string \| null` | `null` | Unique-per-request nonce for public-key-auth payouts |
|
|
59
|
+
| `setPublicKeyAuthenticationSignatureBase64` | `string \| null` | `null` | Usually set via `generatePublicKeyAuthenticationSignature` |
|
|
60
|
+
| `setPrivateKeyFileLocation` | `string \| null` | `null` | Path to the signing private key (PEM file) |
|
|
61
|
+
| `setPrivateKeyContent` | `string \| null` | `null` | Key PEM text; for serverless hosts without key files (wins over file location) |
|
|
62
|
+
| `setPublicKeyFileUrl` | `string` | bundled cert | Certificate used to verify IPN signatures |
|
|
63
|
+
| `setUrl` | `string` | gateway URL | Override the API endpoint (testing/proxies) |
|
|
64
|
+
| `setTimeout` | `number` (ms) | `120000` | Request timeout; `<= 0` disables it |
|
|
65
|
+
| `setTlsVerificationEnabled` | `boolean` | `true` | Only disable for testing against self-signed endpoints |
|
|
66
|
+
| `setMaxResponseBytes` | `number` | `1048576` | Cap on gateway response bodies |
|
|
63
67
|
|
|
64
68
|
Every setter has a matching getter (`getExternalReference()`, `getMode()`, …). One instance holds per-request state, so create a fresh client per request — never share one across concurrent operations.
|
|
65
69
|
|
|
@@ -76,14 +80,18 @@ Amounts accept `number | string` — pass a string when exact formatting matters
|
|
|
76
80
|
### acDepositFunds — request a mobile money deposit (USSD PIN prompt)
|
|
77
81
|
|
|
78
82
|
```ts
|
|
79
|
-
const res: DepositFundsResponse = await yoAPI.acDepositFunds(
|
|
83
|
+
const res: DepositFundsResponse = await yoAPI.acDepositFunds(
|
|
84
|
+
msisdn,
|
|
85
|
+
amount,
|
|
86
|
+
narrative,
|
|
87
|
+
);
|
|
80
88
|
```
|
|
81
89
|
|
|
82
|
-
| Parameter
|
|
83
|
-
|
|
84
|
-
| `msisdn`
|
|
85
|
-
| `amount`
|
|
86
|
-
| `narrative` | `string`
|
|
90
|
+
| Parameter | Type | Description |
|
|
91
|
+
| ----------- | ------------------ | --------------------------------------- |
|
|
92
|
+
| `msisdn` | `string` | Subscriber phone, e.g. `"256770000000"` |
|
|
93
|
+
| `amount` | `number \| string` | Amount to collect |
|
|
94
|
+
| `narrative` | `string` | Reason shown to the subscriber |
|
|
87
95
|
|
|
88
96
|
Response (`DepositFundsResponse`): `Status`, `StatusCode`, `StatusMessage`, `TransactionStatus` always present. On success also `TransactionReference` (save this — it identifies the payment everywhere else), `MNOTransactionReferenceId`, `IssuedReceiptNumber`. On business failure, `ErrorMessageCode` / `ErrorMessage` instead. Optional request tweaks: `setNonblocking("TRUE")` + IPN URLs, `setAuthenticationSignatureBase64`.
|
|
89
97
|
|
|
@@ -143,7 +151,13 @@ const res: MinistatementResponse = await yoAPI.acGetMinistatement(
|
|
|
143
151
|
// to a phone number
|
|
144
152
|
await yoAPI.acSendAirtimeMobile(msisdn, amount, narrative);
|
|
145
153
|
// to another Yo! account ("UGX-MTNAT" | "UGX-WTLAT" | "UGX-OULAT" | "UGX-AIRAT")
|
|
146
|
-
await yoAPI.acSendAirtimeInternal(
|
|
154
|
+
await yoAPI.acSendAirtimeInternal(
|
|
155
|
+
currencyCode,
|
|
156
|
+
amount,
|
|
157
|
+
beneficiaryAccount,
|
|
158
|
+
beneficiaryEmail,
|
|
159
|
+
narrative,
|
|
160
|
+
);
|
|
147
161
|
```
|
|
148
162
|
|
|
149
163
|
Same response shape as deposits.
|
|
@@ -151,7 +165,11 @@ Same response shape as deposits.
|
|
|
151
165
|
### acWithdrawFunds — pay out to mobile money (handle with care)
|
|
152
166
|
|
|
153
167
|
```ts
|
|
154
|
-
const res: DepositFundsResponse = await yoAPI.acWithdrawFunds(
|
|
168
|
+
const res: DepositFundsResponse = await yoAPI.acWithdrawFunds(
|
|
169
|
+
msisdn,
|
|
170
|
+
amount,
|
|
171
|
+
narrative,
|
|
172
|
+
);
|
|
155
173
|
```
|
|
156
174
|
|
|
157
175
|
Same response shape as deposits. Requires an API Access Letter; some payouts additionally require public-key authentication — see below. Optional: `setTransactionLimitAccountIdentifier`, `setPublicKeyAuthenticationNonce` + `setPublicKeyAuthenticationSignatureBase64`.
|
|
@@ -170,7 +188,8 @@ const res: PurchaseAirtimeStockResponse = await yoAPI.acUserPurchaseAirtimestock
|
|
|
170
188
|
### acGetMsisdnKycInfo — name lookup before paying out
|
|
171
189
|
|
|
172
190
|
```ts
|
|
173
|
-
const res: MsisdnKycInfoResponse =
|
|
191
|
+
const res: MsisdnKycInfoResponse =
|
|
192
|
+
await yoAPI.acGetMsisdnKycInfo("256770000000");
|
|
174
193
|
// res.FirstName / res.MiddleName / res.Surname when the gateway returns them
|
|
175
194
|
```
|
|
176
195
|
|
|
@@ -180,12 +199,21 @@ MTN Uganda and Airtel Uganda only; needs permission from support@yo.co.ug. `Stat
|
|
|
180
199
|
|
|
181
200
|
```ts
|
|
182
201
|
const payment: PaymentNotificationResult = yoAPI.receivePaymentNotification({
|
|
183
|
-
|
|
202
|
+
date_time,
|
|
203
|
+
amount,
|
|
204
|
+
narrative,
|
|
205
|
+
network_ref,
|
|
206
|
+
external_ref,
|
|
207
|
+
msisdn,
|
|
208
|
+
signature,
|
|
184
209
|
});
|
|
185
210
|
// payment.is_verified === true → trust payment.msisdn / .amount / .external_ref / ...
|
|
186
|
-
const failure: PaymentFailureNotificationResult =
|
|
187
|
-
|
|
188
|
-
|
|
211
|
+
const failure: PaymentFailureNotificationResult =
|
|
212
|
+
yoAPI.receivePaymentFailureNotification({
|
|
213
|
+
failed_transaction_reference,
|
|
214
|
+
transaction_init_date,
|
|
215
|
+
verification,
|
|
216
|
+
});
|
|
189
217
|
```
|
|
190
218
|
|
|
191
219
|
Pass the parsed POST form body (PHP reads `$_POST`; here you supply it). Verification is RSA-SHA256 against the bundled Yo! certificate and is fail-closed: any problem (bad signature, missing cert) yields `is_verified: false`, never a throw. Always gate crediting on `is_verified` **and** dedupe on `external_ref` — notifications carry no replay protection.
|
|
@@ -207,6 +235,7 @@ Signs `username + amount + msisdn + narrative + externalReference + nonce` (SHA1
|
|
|
207
235
|
Concrete objects each call resolves to. Absent optional fields are omitted (never `null`).
|
|
208
236
|
|
|
209
237
|
**Deposits, transfers, airtime, withdrawals** (`DepositFundsResponse` family) — success:
|
|
238
|
+
|
|
210
239
|
```ts
|
|
211
240
|
{
|
|
212
241
|
Status: "OK",
|
|
@@ -220,6 +249,7 @@ Concrete objects each call resolves to. Absent optional fields are omitted (neve
|
|
|
220
249
|
```
|
|
221
250
|
|
|
222
251
|
Same calls — business failure (returned, not thrown):
|
|
252
|
+
|
|
223
253
|
```ts
|
|
224
254
|
{
|
|
225
255
|
Status: "FAILED",
|
|
@@ -232,6 +262,7 @@ Same calls — business failure (returned, not thrown):
|
|
|
232
262
|
```
|
|
233
263
|
|
|
234
264
|
**Transaction status** (`TransactionCheckStatusResponse`) — success carries the money fields:
|
|
265
|
+
|
|
235
266
|
```ts
|
|
236
267
|
{
|
|
237
268
|
Status: "OK",
|
|
@@ -249,6 +280,7 @@ Same calls — business failure (returned, not thrown):
|
|
|
249
280
|
```
|
|
250
281
|
|
|
251
282
|
Still pending — keep polling:
|
|
283
|
+
|
|
252
284
|
```ts
|
|
253
285
|
{
|
|
254
286
|
Status: "OK",
|
|
@@ -259,6 +291,7 @@ Still pending — keep polling:
|
|
|
259
291
|
```
|
|
260
292
|
|
|
261
293
|
**Balance** (`AcctBalanceResponse`):
|
|
294
|
+
|
|
262
295
|
```ts
|
|
263
296
|
{
|
|
264
297
|
Status: "OK",
|
|
@@ -271,6 +304,7 @@ Still pending — keep polling:
|
|
|
271
304
|
```
|
|
272
305
|
|
|
273
306
|
**Ministatement** (`MinistatementResponse`) — `Transactions` is always an array:
|
|
307
|
+
|
|
274
308
|
```ts
|
|
275
309
|
{
|
|
276
310
|
Status: "OK",
|
|
@@ -302,6 +336,7 @@ Still pending — keep polling:
|
|
|
302
336
|
```
|
|
303
337
|
|
|
304
338
|
**Airtimestock purchase** (`PurchaseAirtimeStockResponse`):
|
|
339
|
+
|
|
305
340
|
```ts
|
|
306
341
|
{
|
|
307
342
|
Status: "OK",
|
|
@@ -314,6 +349,7 @@ Still pending — keep polling:
|
|
|
314
349
|
```
|
|
315
350
|
|
|
316
351
|
**KYC lookup** (`MsisdnKycInfoResponse`):
|
|
352
|
+
|
|
317
353
|
```ts
|
|
318
354
|
{
|
|
319
355
|
Status: "OK",
|
|
@@ -326,6 +362,7 @@ Still pending — keep polling:
|
|
|
326
362
|
```
|
|
327
363
|
|
|
328
364
|
**Verified payment notification** (`PaymentNotificationResult`):
|
|
365
|
+
|
|
329
366
|
```ts
|
|
330
367
|
{
|
|
331
368
|
is_verified: true,
|
|
@@ -339,6 +376,7 @@ Still pending — keep polling:
|
|
|
339
376
|
```
|
|
340
377
|
|
|
341
378
|
Unverifiable notification (bad signature or cert problem) — credit nothing:
|
|
379
|
+
|
|
342
380
|
```ts
|
|
343
381
|
{
|
|
344
382
|
is_verified: false,
|
|
@@ -352,6 +390,7 @@ Unverifiable notification (bad signature or cert problem) — credit nothing:
|
|
|
352
390
|
```
|
|
353
391
|
|
|
354
392
|
**Transport failure** — thrown as `YoAPIError`, e.g. gateway HTTP 502:
|
|
393
|
+
|
|
355
394
|
```ts
|
|
356
395
|
// caught error instance:
|
|
357
396
|
YoAPIError: Yo! Payments gateway responded with HTTP 502
|
|
@@ -363,18 +402,20 @@ YoAPIError: Yo! Payments gateway responded with HTTP 502
|
|
|
363
402
|
## Usage cases
|
|
364
403
|
|
|
365
404
|
**1. Blocking deposit** — simplest collection flow; the call returns after the subscriber approves:
|
|
405
|
+
|
|
366
406
|
```ts
|
|
367
407
|
const api = new YoAPI(u, p, "sandbox");
|
|
368
408
|
api.setExternalReference(`INV-${Date.now()}`);
|
|
369
409
|
const res = await api.acDepositFunds("256770000000", 10000, "Order payment");
|
|
370
410
|
if (res.Status === "OK" && res.TransactionStatus === "SUCCEEDED") {
|
|
371
|
-
|
|
411
|
+
await markPaid(res.TransactionReference!);
|
|
372
412
|
} else {
|
|
373
|
-
|
|
413
|
+
console.error(res.ErrorMessageCode, res.ErrorMessage);
|
|
374
414
|
}
|
|
375
415
|
```
|
|
376
416
|
|
|
377
417
|
**2. Non-blocking deposit with IPN + polling fallback** — instant response, then confirm:
|
|
418
|
+
|
|
378
419
|
```ts
|
|
379
420
|
api.setNonblocking("TRUE");
|
|
380
421
|
api.setInstantNotificationUrl("https://example.com/api/yo/ipn");
|
|
@@ -383,19 +424,27 @@ const res = await api.acDepositFunds("256770000000", 10000, "Order payment");
|
|
|
383
424
|
// ...meanwhile your IPN endpoint verifies and credits on payment.external_ref...
|
|
384
425
|
// ...and/or poll until settled:
|
|
385
426
|
for (;;) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
427
|
+
const st = await api.acTransactionCheckStatus(null, externalRef);
|
|
428
|
+
if (st.TransactionStatus !== "PENDING") break;
|
|
429
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
389
430
|
}
|
|
390
431
|
```
|
|
391
432
|
|
|
392
433
|
**3. Daily reconciliation from the ministatement:**
|
|
434
|
+
|
|
393
435
|
```ts
|
|
394
|
-
const st = await api.acGetMinistatement(
|
|
436
|
+
const st = await api.acGetMinistatement(
|
|
437
|
+
"2026-09-10 00:00:00",
|
|
438
|
+
"2026-09-10 23:59:59",
|
|
439
|
+
"SUCCEEDED",
|
|
440
|
+
"UGX-MTNMM",
|
|
441
|
+
0,
|
|
442
|
+
);
|
|
395
443
|
for (const tx of st.Transactions) await reconcile(tx);
|
|
396
444
|
```
|
|
397
445
|
|
|
398
446
|
**4. Serverless payout with key material (no key files on Vercel/Lambda):**
|
|
447
|
+
|
|
399
448
|
```ts
|
|
400
449
|
api.setExternalReference("SAL-SEP-001");
|
|
401
450
|
api.setPublicKeyAuthenticationNonce(crypto.randomUUID());
|
|
@@ -411,22 +460,24 @@ PHP reads `$_POST` / `php://input` globals, which is impossible in TypeScript, s
|
|
|
411
460
|
```ts
|
|
412
461
|
// Bun HTTP server example
|
|
413
462
|
Bun.serve({
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
463
|
+
port: 3000,
|
|
464
|
+
async fetch(req) {
|
|
465
|
+
const form = await req.formData();
|
|
466
|
+
const body = Object.fromEntries(form.entries()) as any;
|
|
467
|
+
|
|
468
|
+
const yoAPI = new YoAPI("API_USERNAME", "API_PASSWORD", "sandbox");
|
|
469
|
+
const payment = yoAPI.receivePaymentNotification(body);
|
|
470
|
+
if (payment.is_verified) {
|
|
471
|
+
console.log(
|
|
472
|
+
`Payment from ${payment.msisdn} of ${payment.amount} (ref ${payment.external_ref})`,
|
|
473
|
+
);
|
|
474
|
+
// update your transaction status where external_ref = payment.external_ref
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Failure notifications:
|
|
478
|
+
// const failure = yoAPI.receivePaymentFailureNotification(body);
|
|
479
|
+
return new Response("OK");
|
|
480
|
+
},
|
|
430
481
|
});
|
|
431
482
|
```
|
|
432
483
|
|
|
@@ -444,7 +495,11 @@ import "server-only";
|
|
|
444
495
|
import { YoAPI } from "@herberthtk/yo-payments-api";
|
|
445
496
|
|
|
446
497
|
export function getYoClient() {
|
|
447
|
-
|
|
498
|
+
return new YoAPI(
|
|
499
|
+
process.env.YO_API_USERNAME!,
|
|
500
|
+
process.env.YO_API_PASSWORD!,
|
|
501
|
+
"sandbox",
|
|
502
|
+
);
|
|
448
503
|
}
|
|
449
504
|
```
|
|
450
505
|
|
|
@@ -453,27 +508,18 @@ export function getYoClient() {
|
|
|
453
508
|
import "server-only";
|
|
454
509
|
import { getYoClient } from "@/lib/yo";
|
|
455
510
|
|
|
456
|
-
export const runtime = "nodejs";
|
|
457
511
|
export const dynamic = "force-dynamic";
|
|
512
|
+
export const runtime = "nodejs";
|
|
458
513
|
|
|
459
514
|
export async function POST(req: Request) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
network_ref: body.network_ref ?? "",
|
|
469
|
-
external_ref: body.external_ref ?? "",
|
|
470
|
-
msisdn: body.msisdn ?? "",
|
|
471
|
-
signature: body.signature ?? "",
|
|
472
|
-
});
|
|
473
|
-
if (!payment.is_verified) return new Response("NOT VERIFIED", { status: 400 });
|
|
474
|
-
|
|
475
|
-
// TODO: persist + mark processed idempotently on payment.external_ref
|
|
476
|
-
return new Response("OK");
|
|
515
|
+
const form = await req.formData();
|
|
516
|
+
const body = Object.fromEntries(form.entries()) as any;
|
|
517
|
+
const payment = getYoClient().receivePaymentNotification(body);
|
|
518
|
+
if (!payment.is_verified)
|
|
519
|
+
return new Response("NOT VERIFIED", { status: 400 });
|
|
520
|
+
|
|
521
|
+
// TODO: persist + mark processed idempotently on payment.external_ref
|
|
522
|
+
return new Response("OK");
|
|
477
523
|
}
|
|
478
524
|
```
|
|
479
525
|
|
|
@@ -482,12 +528,17 @@ export async function POST(req: Request) {
|
|
|
482
528
|
"use server";
|
|
483
529
|
import { getYoClient } from "@/lib/yo";
|
|
484
530
|
|
|
485
|
-
export async function requestDeposit(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
531
|
+
export async function requestDeposit(
|
|
532
|
+
msisdn: string,
|
|
533
|
+
amount: number,
|
|
534
|
+
narrative: string,
|
|
535
|
+
) {
|
|
536
|
+
const api = getYoClient();
|
|
537
|
+
api.setExternalReference(`${Date.now()}`);
|
|
538
|
+
const res = await api.acDepositFunds(msisdn, amount, narrative);
|
|
539
|
+
if (res.Status === "OK")
|
|
540
|
+
return { ok: true, reference: res.TransactionReference };
|
|
541
|
+
return { ok: false, message: res.StatusMessage };
|
|
491
542
|
}
|
|
492
543
|
```
|
|
493
544
|
|
|
@@ -496,8 +547,14 @@ export async function requestDeposit(msisdn: string, amount: number, narrative:
|
|
|
496
547
|
import { getYoClient } from "@/lib/yo";
|
|
497
548
|
|
|
498
549
|
export default async function StatementPage() {
|
|
499
|
-
|
|
500
|
-
|
|
550
|
+
const res = await getYoClient().acGetMinistatement(
|
|
551
|
+
null,
|
|
552
|
+
null,
|
|
553
|
+
"SUCCEEDED",
|
|
554
|
+
"UGX-MTNMM",
|
|
555
|
+
0,
|
|
556
|
+
);
|
|
557
|
+
return <pre>{JSON.stringify(res.Transactions, null, 2)}</pre>;
|
|
501
558
|
}
|
|
502
559
|
```
|
|
503
560
|
|
|
@@ -511,11 +568,11 @@ Transport-level and protocol-level failures throw `YoAPIError` (an `Error` subcl
|
|
|
511
568
|
import { YoAPI, YoAPIError } from "@herberthtk/yo-payments-api";
|
|
512
569
|
|
|
513
570
|
try {
|
|
514
|
-
|
|
571
|
+
await yoAPI.acAcctBalance();
|
|
515
572
|
} catch (e) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
573
|
+
if (e instanceof YoAPIError) {
|
|
574
|
+
console.error(e.message, "status:", e.status, "cause:", e.cause);
|
|
575
|
+
}
|
|
519
576
|
}
|
|
520
577
|
```
|
|
521
578
|
|
|
@@ -566,7 +623,7 @@ The suite (`tests/YoAPI.test.ts`, `tests/examples.test.ts`, `tests/keys.test.ts`
|
|
|
566
623
|
|
|
567
624
|
### Releasing (maintainers)
|
|
568
625
|
|
|
569
|
-
Versions follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:` → minor, `fix:` → patch, `feat!:`/`BREAKING CHANGE:` → major).
|
|
626
|
+
Versions follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:` → minor, `fix:` → patch, `feat!:`/`BREAKING CHANGE:` → major). Before the first release, configure npm trusted publishing for `@herberthtk/yo-payments-api` in npm package settings: select GitHub Actions, set the repository to `herberthk/yo-payments-api`, and set the workflow filename to `release.yml` (not its full path). See npm's [trusted publishers guide](https://docs.npmjs.com/trusted-publishers/) for the full setup. Then 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).
|
|
570
627
|
|
|
571
628
|
## Project structure
|
|
572
629
|
|
package/dist/index.cjs
CHANGED
|
@@ -815,12 +815,12 @@ var YoAPI = class {
|
|
|
815
815
|
maxResponseBytes: this.maxResponseBytes
|
|
816
816
|
});
|
|
817
817
|
}
|
|
818
|
-
/** Verify the RSA-
|
|
818
|
+
/** Verify the RSA-SHA1 signature on a payment notification against the Yo public certificate. */
|
|
819
819
|
verifyPaymentNotification(body) {
|
|
820
820
|
const data = (body.date_time ?? "") + (body.amount ?? "") + (body.narrative ?? "") + (body.network_ref ?? "") + (body.external_ref ?? "") + (body.msisdn ?? "");
|
|
821
821
|
return this.verifySignature(data, body.signature);
|
|
822
822
|
}
|
|
823
|
-
/** Verify the RSA-
|
|
823
|
+
/** Verify the RSA-SHA1 signature on a payment failure notification against the Yo public certificate. */
|
|
824
824
|
verifyPaymentFailureNotification(body) {
|
|
825
825
|
const data = (body.failed_transaction_reference ?? "") + (body.transaction_init_date ?? "");
|
|
826
826
|
return this.verifySignature(data, body.verification);
|
|
@@ -833,7 +833,9 @@ var YoAPI = class {
|
|
|
833
833
|
);
|
|
834
834
|
if (publicKey === null) return false;
|
|
835
835
|
try {
|
|
836
|
-
|
|
836
|
+
const dataBuf = Buffer.from(data, "utf-8");
|
|
837
|
+
const sigBuf = Buffer.from(signatureBase64, "base64");
|
|
838
|
+
return (0, import_node_crypto2.verify)("sha1", dataBuf, publicKey, sigBuf) || (0, import_node_crypto2.verify)("sha256", dataBuf, publicKey, sigBuf);
|
|
837
839
|
} catch {
|
|
838
840
|
return false;
|
|
839
841
|
}
|