Verify Ethiopian CBE & Telebirr payment receipts from a pasted SMS - no bank API keys, no manual review.
# paste-to-verify
Verify Ethiopian **CBE** and **Telebirr** payment receipts from a pasted SMS — no
bank API keys, no manual review.
When a customer pays, they get an SMS containing a receipt link. This library
takes that raw SMS, extracts the link, fetches the **authoritative receipt page**,
parses it into a normalized transaction, and runs deterministic checks.
```ts
import { parseReceipt, verifyTransaction } from 'paste-to-verify';
const transaction = await parseReceipt(pastedSms);
const result = verifyTransaction(transaction, {
amount: 990,
receiverName: 'Dawit',
maxAgeMinutes: 30,
});
result.verified; // boolean
result.failures; // [{ field, expected, actual, reason }]
```
## Why a receipt page and not the SMS?
The SMS is **not** trusted as the source of truth — it is easy to fabricate. It
serves two purposes only: it supplies the receipt URL, and it provides a fallback
for fields the receipt page doesn't expose. The receipt page is authoritative, so
on any overlap **the page value wins**.
## Providers
| Provider | Host | Receipt page | Fetch strategy |
| ------------ | --------------------------------- | ---------------------------------- | -------------------------------------------- |
| **Telebirr** | `transactioninfo.ethiotelecom.et` | Server-rendered HTML | `fetch` + `node-html-parser` (edge-friendly) |
| **CBE** | `cbe.com.et` | JavaScript SPA (spinner-only HTML) | Headless browser render, then scrape the DOM |
The CBE receipt is a Nuxt SPA whose served HTML contains only a loading spinner,
so it is rendered with **Playwright** (by default) and the loaded DOM is scraped.
You can inject your own renderer for edge runtimes — see
`apps/docs` → _Providers → CBE_.
> CBE therefore needs a headless browser. Telebirr alone does not.
## Monorepo layout
```
packages/core/ # the published npm package: paste-to-verify
a …