Odit Verify

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
PartPatternExample
hostliteralshare.zemenbank.com
<prefix>2 lettersrt (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.

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)

{
  "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.20,
  "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. v.odit.et's outbound queue serialises calls (one in flight at a time) for this host and cooldowns aggressively on 429.

On this page