@bevingh/money
v0.1.0
Published
Integer-pesewa conversions, validators, and formatting helpers for pesewa_end_to_end apps (not GHS-float migration).
Readme
@bevingh/money
Phase 3 / PR-12 — extracted. Pure integer-pesewa helpers for pesewa_end_to_end apps only. No Express adapter (
pure_core).
Purpose
Integer-pesewa conversions, validators, and formatting helpers for pesewa_end_to_end apps (not GHS-float migration).
| Field | Value |
|---|---|
| surfaceShape | pure_core |
| targetRuntime | typescript_esm |
| extractionOrderHint | 2 |
| dependsOnPackages | (none) |
| status | extracted (implementation + tests) |
Deliberately narrow scope
This package encodes the standing rule: monetary values as integers in the smallest unit (pesewas) end-to-end, not only at a payment-API boundary (roomsplit docs/02-SCHEMA.md §8).
Permanent out-of-scope boundaries (do not “resolve and remove”)
These are not a temporary extraction checklist. They stay documented forever for this package:
| Boundary | Why |
|---|---|
| ghs_float_at_boundary (8 apps: bevin-events, conduit, payment-gatway, ussd-service, UVT, Academicx, imep-portal-api, Texify payment amounts) | Those apps store GHS floats and convert only at the provider edge. They need a convention migration before adopting @bevingh/money — this package is not a drop-in for them. |
| Texify SMS credit balance | Product-specific credit units, not GHS/pesewas. Stays keep_project_specific. |
| Accounting LedgerEntry Number amounts (conduit / payment-gatway) | Tax/P&L bookkeeping in major units — different convention from wallet pesewas (@bevingh/ledger / document-only accounting). |
| Provider API call logic | Paystack/Moolre/etc. live in @bevingh/payments or apps — not here. |
Likely consumers today: roomsplit, Didipay, mirrly, maame, Bevin-Photos (pesewa_end_to_end cluster only).
Public API
| Export | Role |
|---|---|
| pesewasToGhs(pesewas) | Integer pesewas → "12.50" string (mirrly) |
| ghsToPesewas(ghs) | GHS string or number → integer pesewas (Math.round(*100)) |
| isPesewas(value, opts?) | Type guard: finite safe integer; non-negative by default |
| assertPesewas(value, opts?) | Same, throws TypeError on failure |
| formatPesewasAsGhs(pesewas, opts?) | Display helper, default "GH₵12.50" |
| PesewasValidationOptions, FormatPesewasOptions | Types |
import {
pesewasToGhs,
ghsToPesewas,
assertPesewas,
formatPesewasAsGhs,
} from '@bevingh/money';
assertPesewas(1250); // 1250
pesewasToGhs(1250); // "12.50"
ghsToPesewas("12.50"); // 1250
formatPesewasAsGhs(1250); // "GH₵12.50"
formatPesewasAsGhs(1250, { symbol: 'GHS ' }); // "GHS 12.50" (Didipay USSD style)
assertPesewas(0); // ok (wallet default)
assertPesewas(1, { allowZero: false }); // roomsplit budget-style positiveValidation options
| Option | Default | Notes |
|---|---|---|
| allowNegative | false | Set true for signed deltas only |
| allowZero | true | Set false for roomsplit .positive() budget fields |
Champion files (read-only references)
| Path | What was taken |
|---|---|
| mirrly moolre-utils.ts | Conversion pesewasToGhs / ghsToPesewas |
| mirrly db.schema.ts | integer columns with // pesewas comments |
| Didipay schema.prisma | Int // pesewas wallet/txn fields |
| roomsplit User.js + profile.validators.js | budget_*_pesewas + .int() / .positive() |
| roomsplit docs/02-SCHEMA.md | standing rule text + display example GH₵ |
Display formatting pattern also observed in Didipay ussd.service.ts / moolre.service.ts ((n/100).toFixed(2) + GHS label) — not listed as a formal champion file but used as the operational format source.
Tests
No champion unit tests existed (championTestPaths: "no tests found"). Package tests are new:
npm run test -w @bevingh/money
npm run build -w @bevingh/moneyCoverage: round-trip conversion, non-integer rejection, NaN/Infinity/negative rejection, format zero / mid / large amounts.
Layout
src/index.ts # pure core only (KD13: no adapters/)
test/money.test.tsmustNotContain (verified)
| Constraint | Status | |---|---| | ghs_float_at_boundary domain models | OK — not present; boundary documented permanently | | SMS credit units (Texify) | OK | | accounting LedgerEntry Number amounts | OK | | provider API call logic | OK |
