# Zemen Bank

> Detail page: URL token structure (24-char prefix + reference composite), PDF parsing quirks, and full return type.

Zemen's `share.zemenbank.com` serves a PDF directly (with a download attachment header). We grab the bytes, parse with `pdf-parse`, and extract structured fields.

## URL format

```
https://share.zemenbank.com/<prefix>/<token>/pdf
```

| Part | Pattern | Example |
| ---------- | ---------------------- | ------------------------------------------------------------------------- |
| host | literal | `share.zemenbank.com` |
| `<prefix>` | 2 letters | `rt` (retail / ATM) · `ft` (funds transfer) · others observed in the wild |
| `<token>` | 24 chars `[A-Za-z0-9]` | `00000000ABCDEFGHIJKLMNOP` |

### Token anatomy

The 24-char token is `<8-char prefix><16-char reference>`. The trailing 16 chars are the actual **reference number** that appears in the receipt body under "Reference No". The leading 8 chars look like a branch / timestamp code that's only used at the URL level.

```
00000000ABCDEFGHIJKLMNOP
└──────┬─┘└──────┬─────┘
 8-char        16-char
 URL only      reference (echoed in receipt)
```

The resolver accepts **any 2-letter prefix**, so when Zemen mints a new code (`cr`, `dr`, etc.) you don't need a redeploy on our side.

## Return type

`receipt.source === "zemen-pdf"`. Amounts are numbers; currency is always `"ETB"` on these receipts.

```ts
interface ZemenReceipt {
	source: 'zemen-pdf';

	// Header
	invoiceNo?: string;
	date?: string; // e.g. "3-Jun-2026"

	// Counterparties
	payerName?: string;
	payerAccount?: string; // e.g. "173****0000(923141XXXXXX0000)"
	payerTin?: string; // empty unless payer is VAT-registered
	payerVat?: string;
	recipientName?: string;
	recipientAccount?: string; // masked

	// Transaction
	paymentOrderNumber?: string;
	reference?: string; // last 16 chars of the URL token
	transactionStatus?: string; // "COMPLETED" | …
	transactionDetail?: string; // e.g. "Remot On Us - ATM CASH WITHDRAWAL"

	// Amounts (numeric)
	settledAmount?: number;
	serviceCharge?: number;
	vat?: number; // 15%
	disasterRiskCharge?: number; // 5%
	totalAmountPaid?: number;
	currency?: 'ETB'; // always ETB on these receipts

	// Misc
	totalAmountInWord?: string; // e.g. "ONE HUNDRED ONE BIRR AND TWENTY CENT(S)"
	paymentReason?: string; // free text, often empty
}
```

### Sample (placeholder values)

```json
{
	"source": "zemen-pdf",
	"invoiceNo": "000000000",
	"date": "1-Jan-2026",
	"payerName": "<payer name>",
	"payerAccount": "173****0000(923141XXXXXX0000)",
	"payerTin": "",
	"payerVat": "",
	"recipientName": "<recipient name>",
	"recipientAccount": "00****00",
	"paymentOrderNumber": "",
	"reference": "ABCDEFGHIJKLMNOP",
	"transactionStatus": "COMPLETED",
	"transactionDetail": "Remot On Us - ATM CASH WITHDRAWAL",
	"settledAmount": 100,
	"serviceCharge": 1,
	"vat": 0.15,
	"disasterRiskCharge": 0.05,
	"totalAmountPaid": 101.2,
	"currency": "ETB",
	"totalAmountInWord": "ONE HUNDRED ONE BIRR AND TWENTY CENT(S)",
	"paymentReason": ""
}
```

## Quirks

- **`Date` field uses the Amharic colon**: the top-of-document date label ends in `፡` (U+1361), not ASCII `:`. Our regex accepts both.
- **No whitespace between table cells**: pdf-parse strips spacing such that `Service ChargeETB 1.00` arrives as one token. Amount-row regex uses `[ \t]*` between the label and `ETB`, not `\s+`.
- **Empty-value rows collapse onto the next label**: when a field like "Payer TIN" is unset, pdf-parse emits the label followed immediately by a newline. Our label regex uses `[ \t]*` for the trailing whitespace so we don't slurp the next label's value into the current field.
- **Some receipts return `HTTP 404 application/json`** when the reference is no longer available upstream (Zemen cycles them out of cache). We detect non-PDF bodies and surface a friendly `"receipt not found upstream"` error instead of a misleading `"pdf-parse failed"`.
- **Real fetches are slow**: Zemen's PDF backend takes 5–15 s per call even when healthy. links.et's outbound queue serialises calls (one in flight at a time) for this host and cooldowns aggressively on `429`.
