PHPackages                             melaku/telebirr - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. melaku/telebirr

ActiveLibrary[Payment Processing](/categories/payments)

melaku/telebirr
===============

Telebirr Web Checkout PHP library (modern API, C2B Web Checkout).

2.2.0(1mo ago)162.7k—10%3MITPHPPHP &gt;=7.4.0

Since Dec 17Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/MelakuDemeke/telebirr-php)[ Packagist](https://packagist.org/packages/melaku/telebirr)[ RSS](/packages/melaku-telebirr/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (6)Dependencies (4)Versions (11)Used By (0)

[ ![Telebirr](img/telebirrlogo.png "Aimeos")](https://aimeos.org/)Telebirr PHP Library (Web Checkout)
===================================

[](#telebirr-php-library-web-checkout)

[![](img/telebanner.png)](img/telebanner.png)

[![GitHub branch checks state](https://camo.githubusercontent.com/cc1b138c70ef31a778279e55b37c5e12346efa9cc563220d37969d59b5004a1e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636865636b732d7374617475732f4d656c616b7544656d656b652f74656c65626972722d7068702f6d61696e)](https://camo.githubusercontent.com/cc1b138c70ef31a778279e55b37c5e12346efa9cc563220d37969d59b5004a1e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636865636b732d7374617475732f4d656c616b7544656d656b652f74656c65626972722d7068702f6d61696e)[![GitHub repo size](https://camo.githubusercontent.com/60c7a3abbfe58511f5b426a6271d4e13d57305f7ba6402ef0a5809df55c784ca/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f7265706f2d73697a652f4d656c616b7544656d656b652f74656c65626972722d706870)](https://camo.githubusercontent.com/60c7a3abbfe58511f5b426a6271d4e13d57305f7ba6402ef0a5809df55c784ca/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f7265706f2d73697a652f4d656c616b7544656d656b652f74656c65626972722d706870)[![GitHub issues](https://camo.githubusercontent.com/4edc6226d5ac0bfa1f432d465a52fbe554707e91eb641f2bf05c0847dc3d43c0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f4d656c616b7544656d656b652f74656c65626972722d706870)](https://camo.githubusercontent.com/4edc6226d5ac0bfa1f432d465a52fbe554707e91eb641f2bf05c0847dc3d43c0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f4d656c616b7544656d656b652f74656c65626972722d706870)[![Packagist Downloads](https://camo.githubusercontent.com/700f3fe969d90417650f4ca9535c4359e94e26a0090ea4d453514e64270b6b6b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d656c616b752f74656c65626972723f636f6c6f723d677265656e266c6f676f3d7061636b6167697374266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/700f3fe969d90417650f4ca9535c4359e94e26a0090ea4d453514e64270b6b6b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d656c616b752f74656c65626972723f636f6c6f723d677265656e266c6f676f3d7061636b6167697374266c6f676f436f6c6f723d7768697465)[![Packagist Stars](https://camo.githubusercontent.com/4fe5520843bc2ad7c241aefbef9e0e24ecb1b6becae4221d6bdde52fdaf6f15f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f73746172732f6d656c616b752f74656c65626972723f6c6f676f3d7061636b6167697374266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/4fe5520843bc2ad7c241aefbef9e0e24ecb1b6becae4221d6bdde52fdaf6f15f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f73746172732f6d656c616b752f74656c65626972723f6c6f676f3d7061636b6167697374266c6f676f436f6c6f723d7768697465)[![GitHub](https://camo.githubusercontent.com/a5cb11964c173a547821a94ee9fb119ea23efede8fd9319e98d62fef886ced82/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f4d656c616b7544656d656b652f74656c65626972722d7068703f7374796c653d666c6174)](https://camo.githubusercontent.com/a5cb11964c173a547821a94ee9fb119ea23efede8fd9319e98d62fef886ced82/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f4d656c616b7544656d656b652f74656c65626972722d7068703f7374796c653d666c6174)[![GitHub Repo stars](https://camo.githubusercontent.com/b29578e0b854d3b8c402adc497fe04e88deefae6248b8354e21fb27fb6bb8b13/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f4d656c616b7544656d656b652f74656c65626972722d7068703f6c6f676f3d676974687562267374796c653d666c6174)](https://camo.githubusercontent.com/b29578e0b854d3b8c402adc497fe04e88deefae6248b8354e21fb27fb6bb8b13/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f4d656c616b7544656d656b652f74656c65626972722d7068703f6c6f676f3d676974687562267374796c653d666c6174)[![GitHub forks](https://camo.githubusercontent.com/504a72ea6c72a676c87e2021b430baabd1daa98e83904f6f5349c5e6f373a3ba/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f666f726b732f4d656c616b7544656d656b652f74656c65626972722d7068703f6c6f676f3d676974687562267374796c653d66616c74)](https://camo.githubusercontent.com/504a72ea6c72a676c87e2021b430baabd1daa98e83904f6f5349c5e6f373a3ba/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f666f726b732f4d656c616b7544656d656b652f74656c65626972722d7068703f6c6f676f3d676974687562267374796c653d66616c74)[![GitHub commit activity](https://camo.githubusercontent.com/1332bfcff8398b38e87bfde801014b1ed54f8c50cd280c5278ed18dd2b407e01/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f6d2f4d656c616b7544656d656b652f74656c65626972722d7068703f6c6f676f3d676974687562)](https://camo.githubusercontent.com/1332bfcff8398b38e87bfde801014b1ed54f8c50cd280c5278ed18dd2b407e01/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f6d2f4d656c616b7544656d656b652f74656c65626972722d7068703f6c6f676f3d676974687562)[![GitHub last commit](https://camo.githubusercontent.com/e6fb5faf2cb742e20f5018ce6ea7904dff282e0f0febe62586daa14d0ed01961/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6173742d636f6d6d69742f4d656c616b7544656d656b652f74656c65626972722d706870)](https://camo.githubusercontent.com/e6fb5faf2cb742e20f5018ce6ea7904dff282e0f0febe62586daa14d0ed01961/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6173742d636f6d6d69742f4d656c616b7544656d656b652f74656c65626972722d706870)

A modern PHP library for integrating **Telebirr Web Checkout (C2B)** payments. Telebirr is a mobile money service developed by Huawei and owned by Ethio telecom.

This library provides a simple, easy-to-use API for handling Telebirr payments, fully compliant with the [Telebirr H5 C2B Web Payment Integration Guide](https://developer.ethiotelecom.et/docs/H5%20C2B%20Web%20Payment%20Integration%20Quick%20Guide/requestCreateOrder).

🚀 Quick Start
-------------

[](#-quick-start)

### Installation

[](#installation)

```
composer require melaku/telebirr
```

### Basic Usage

[](#basic-usage)

```
require 'vendor/autoload.php';

use Melaku\Telebirr\Config;
use Melaku\Telebirr\Telebirr;

// Configure (test environment)
$config = Config::forTest([
    'fabricAppId'   => 'YOUR_FABRIC_APP_ID',
    'appSecret'     => 'YOUR_APP_SECRET',
    'merchantAppId' => 'YOUR_MERCHANT_APP_ID',
    'merchantCode'  => 'YOUR_MERCHANT_CODE',
    'privateKey'    => 'YOUR_PRIVATE_KEY_PEM',
    'notifyUrl'     => 'https://your-domain.com/telebirr/notify',
    'redirectUrl'   => 'https://your-domain.com/telebirr/return',
]);

$client = new Telebirr($config);

// Create checkout URL (one line!). Returns a CheckoutResult.
$result = $client->createCheckoutUrl('Order 123', '100.00');

// IMPORTANT: persist the EXACT merch_order_id the library used — Telebirr
// echoes this value back in notifications and on the return URL. Storing a
// different value (e.g. one you thought you passed) can cause lookup misses.
saveOrder($result->getMerchOrderId(), $result->getPrepayId()); // your code

// Redirect customer to Telebirr
header('Location: ' . $result->getCheckoutUrl());
exit;
```

That's it! The library handles token management, order creation, and checkout URL generation automatically.

> **Merchant order id charset:** a merch\_order\_id must match `^[A-Za-z0-9]+$`(ASCII letters and digits only — no `-`, `_`, `.` or spaces). Invalid ids now throw an `InvalidParameterException` instead of being silently rewritten. Pass `null` to have a valid id generated for you, and read it back from the result.

### In-App SDK Payment

[](#in-app-sdk-payment)

If your mobile app's Telebirr SDK initiates the payment instead of a browser redirect, use `createInAppOrder()`. There's no checkout URL for this flow — the response's `receiveCode` must be passed to the mobile SDK to continue the payment.

```
$tokenInfo   = $client->applyFabricToken();
$fabricToken = $tokenInfo['token'];

$order = $client->createInAppOrder($fabricToken, 'Order 123', '100.00');
$receiveCode = $order['biz_content']['receiveCode'];

// Send the receiveCode to your mobile app for the SDK to complete the payment.
header('Content-Type: application/json');
echo json_encode(['receiveCode' => $receiveCode]);
```

📋 Configuration
---------------

[](#-configuration)

### Required Credentials

[](#required-credentials)

You'll receive these from Telebirr:

- `fabricAppId` - Your Fabric App ID (UUID)
- `appSecret` - Your App Secret
- `merchantAppId` - Your Merchant App ID
- `merchantCode` - Your Merchant Code (6-digit)
- `privateKey` - Your RSA Private Key
- `notifyUrl` - Server-to-server notification URL (required)
- `redirectUrl` - User return URL after payment (optional)

### Key formats — bare base64 is fine

[](#key-formats--bare-base64-is-fine)

Ethio Telecom issues merchant keys as **bare base64 DER** (a single long `MIIEvgIBADANBgk…` line, no `-----BEGIN…-----` armor). Pass it exactly as issued — the library normalizes it to PEM automatically, picking the right header (PKCS#8 vs PKCS#1) for you. Proper PEM works too, including PEM whose newlines were flattened to literal `\n` by a `.env` file.

### Environment Setup

[](#environment-setup)

The library automatically uses the correct URLs based on environment:

```
// Test/Development
$config = Config::forTest([...]);

// Production
$config = Config::forProduction([...]);

// Zero-config: read everything from environment variables
$config = Config::fromEnvironment();
```

`Config::fromEnvironment()` reads (any explicit option overrides its variable; `$_ENV`, `$_SERVER`, and `getenv()` are all checked, so it works under php-fpm/Laravel too):

VariableMaps to`TELEBIRR_ENVIRONMENT` (then `APP_ENV`)`environment``TELEBIRR_FABRIC_APP_ID``fabricAppId``TELEBIRR_APP_SECRET``appSecret``TELEBIRR_MERCHANT_APP_ID``merchantAppId``TELEBIRR_MERCHANT_CODE``merchantCode``TELEBIRR_PRIVATE_KEY``privateKey` (PEM or bare base64)`TELEBIRR_NOTIFY_URL``notifyUrl``TELEBIRR_REDIRECT_URL``redirectUrl``TELEBIRR_PUBLIC_KEY``telebirrPublicKey`Default endpoints used by the library:

- Test API:
- Production API:
- Test Web Checkout Redirect: ?
- Production Web Checkout Redirect: ?

💡 Key Features
--------------

[](#-key-features)

- ✅ **Simple API** - One-call checkout (`createCheckoutUrl`) and one-call verification (`getOrderStatus`)
- ✅ **Automatic Token Management** - Fabric tokens are fetched, cached until expiry, and refreshed for you
- ✅ **Key normalization** - Bare base64 keys (as Ethio Telecom issues them) or PEM, both just work
- ✅ **TLS that just works** - Falls back to a bundled Telebirr CA chain when the test gateway's incomplete chain fails the system store; no `verifySsl => false` needed
- ✅ **Structured errors + opt-in retry** - Branch on `$e->getTelebirrCode()`; retry transient sandbox errors with backoff
- ✅ **Signature Verification** - Built-in helpers for return URLs and notifications
- ✅ **Helper Classes** - `ReturnUrlHandler`, `NotificationHandler`, `PaymentStatus`
- ✅ **Environment Support** - Automatic test/production URL handling
- ✅ **Full Compliance** - Follows Telebirr H5 C2B Web Payment Integration spec

📖 Common Use Cases
------------------

[](#-common-use-cases)

### Verify a payment (`getOrderStatus`)

[](#verify-a-payment-getorderstatus)

The one-call, server-to-server way to confirm what actually happened to an order — the verification counterpart to `createCheckoutUrl`. Token handling and response mapping are done for you:

```
$status = $client->getOrderStatus('YOUR_MERCH_ORDER_ID');

$status->paid;           // bool — true ONLY on an explicit success status (fails closed)
$status->tradeStatus;    // e.g. 'PAY_SUCCESS'
$status->amount;         // e.g. '100.00' — VERIFY this against your own order amount
$status->currency;       // 'ETB'
$status->paymentOrderId; // Telebirr's transaction reference (or null)
$status->raw;            // the full queryOrder response if you need more
```

### Handle Payment Return

[](#handle-payment-return)

```
use Melaku\Telebirr\ReturnUrlHandler;

try {
    // Fails closed: throws if the signature is missing or invalid.
    $paymentData = ReturnUrlHandler::handle($_GET, $config);

    if ($paymentData['isSuccess']) {
        // The return URL comes through the user's browser and is spoofable even
        // when signed. For anything that fulfils an order, confirm the real
        // status server-to-server before acting on it:
        $status = $client->getOrderStatus($paymentData['merchantOrderId']);
        if ($status->paid && $status->amount === $expectedAmount) {
            // Update your database / fulfill the order — idempotently (see below).
        }
    }
} catch (\RuntimeException $e) {
    // Missing/invalid signature
    http_response_code(400);
    echo "Invalid payment data";
}
```

#### Return-URL parameters (the raw contract)

[](#return-url-parameters-the-raw-contract)

Telebirr redirects the user's browser to your `redirectUrl` with these query parameters appended (snake\_case):

ParameterMeaning`merch_order_id`Your merchant order id, echoed back verbatim`payment_order_id`Telebirr's transaction reference`trade_status`e.g. `PAY_SUCCESS`, `PAY_FAILED`, `PAY_CANCEL``total_amount`Order amount`trans_currency`Currency (`ETB`)`trans_end_time`Transaction end time`sign`, `sign_type`RSA-PSS signature over the other params`ReturnUrlHandler::handle()` verifies the signature and maps these for you — the table is here for when you're debugging the raw redirect.

### The idempotent settlement pattern (recommended)

[](#the-idempotent-settlement-pattern-recommended)

The browser return and the server notification **race** — either can arrive first, both can arrive, and neither should be trusted on its own. The production-correct shape:

1. On checkout, store a row keyed by `merchOrderId` with `status='pending'`and the expected `amount`.
2. On **both** the return handler and the notify handler, call `$client->getOrderStatus($merchOrderId)` — never trust the callback params.
3. Verify `$status->paid === true` **and** `$status->amount` matches your stored amount.
4. Grant idempotently with a compare-and-set, so the racing paths can't double-fulfill:

```
function settle(Telebirr $client, PDO $db, string $merchOrderId): void
{
    $status = $client->getOrderStatus($merchOrderId);
    if (!$status->paid) {
        return;
    }

    // Atomic claim: only one caller flips pending → success.
    $stmt = $db->prepare(
        "UPDATE orders SET status = 'success'
         WHERE merch_order_id = :id AND status = 'pending' AND amount = :amount"
    );
    $stmt->execute(['id' => $merchOrderId, 'amount' => $status->amount]);

    if ($stmt->rowCount() === 1) {
        fulfillOrder($merchOrderId); // runs exactly once
    }
}
```

#### Notification acknowledgement contract

[](#notification-acknowledgement-contract)

- Telebirr POSTs the notification as a JSON body to your `notifyUrl`.
- Acknowledge success with **HTTP 200** and a JSON body — this is what `NotificationHandler::respondSuccess()` emits: `{"success": true}`.
- Any non-2xx status tells Telebirr the delivery failed; it will **retry the notification** later. Respond 200 once you have durably recorded the event, and reserve error responses for "I could not record this, please retry".
- Your `notifyUrl` must be publicly reachable — `localhost` or a private address will never receive anything (the library warns about this at construction time). In development use a tunnel (ngrok, cloudflared).

### Handle Payment Notifications

[](#handle-payment-notifications)

```
use Melaku\Telebirr\NotificationHandler;

$rawData = file_get_contents('php://input');
$notification = NotificationHandler::parse($rawData);

// Verify signature
if (!NotificationHandler::verify($notification, $config)) {
    // respond* now RETURN a NotificationResponse (no header()/echo). In a
    // framework, convert it to your Response object. In bare PHP, call send().
    NotificationHandler::respondError('Invalid signature')->send();
    exit;
}

// Process payment
if (NotificationHandler::isPaymentSuccessful($notification)) {
    $paymentInfo = NotificationHandler::extractPaymentInfo($notification);
    // Update database, fulfill order, etc.

    NotificationHandler::respondSuccess('Payment processed')->send();
}
```

> **Framework usage:** instead of `->send()`, build a native response, e.g. in Laravel: `return response(json: $resp->getBody(), status: $resp->getStatusCode());`

### Query Order Status (low level)

[](#query-order-status-low-level)

Prefer `getOrderStatus()` above; the raw call remains available when you need the untouched response:

```
$tokenInfo = $client->applyFabricToken();
$orderStatus = $client->queryOrder($tokenInfo['token'], null, 'YOUR_ORDER_ID');

$tradeStatus = $orderStatus['biz_content']['trade_status'] ?? '';
if (strtoupper($tradeStatus) === 'PAY_SUCCESS') {
    // Payment successful
}
```

### Check gateway health

[](#check-gateway-health)

The sandbox can be flaky; probe it before a user-facing checkout if you want to degrade gracefully:

```
$health = $client->ping(); // never throws
if (!$health['ok']) {
    // show "payment temporarily unavailable" instead of a broken checkout
}
```

### Process Refund

[](#process-refund)

```
$tokenInfo = $client->applyFabricToken();
$refundResult = $client->refundOrder(
    $tokenInfo['token'],
    '50.00',              // Refund amount
    'PAYMENT_ORDER_ID',   // or null
    'MERCHANT_ORDER_ID',  // or null
    'Refund reason'       // Optional
);
```

🔧 Requirements
--------------

[](#-requirements)

- PHP &gt;= 7.4
- `ext-curl` extension
- `ext-openssl` extension (used by the legacy `Notify` class for payload decryption only)
- **phpseclib/phpseclib** (^3.0) — **Signer** and **SignatureVerifier** use phpseclib only (pure-PHP). No OpenSSL CLI or ext-openssl required for signing/verification. Works on all platforms including Windows. Algorithm: RSA-PSS, SHA256, MGF1-SHA256, salt length 32.
- **psr/log** (^1.1 || ^2.0 || ^3.0) — the library type-hints the standard `Psr\Log\LoggerInterface`, so any PSR-3 logger (Monolog, Laravel's logger, …) drops straight in.

⚙️ Advanced Configuration
-------------------------

[](#️-advanced-configuration)

### TLS &amp; timeouts

[](#tls--timeouts)

The default HTTP client verifies the gateway's TLS certificate and applies timeouts (a payment gateway must not be called over an unverified or unbounded connection).

**The Telebirr test gateway serves an incomplete certificate chain** (leaf only, missing intermediate), which used to fail verification with cURL error 60 and push people toward `'verifySsl' => false`. The library now **ships the gateway's CA chain** (`src/certs/telebirr-ca.pem`): when system-store verification fails with error 60 and no custom bundle was supplied, the request is retried once against the bundled chain — so verification works out of the box. The bundled chain can only validate hosts issued under it (the Telebirr gateways); it never loosens verification for anything else. If verification still fails, the error explains the options.

```
$config = Config::forProduction([
    // ... credentials ...
    'verifySsl'      => true,   // default true — leave on; the library warns (test) or
                                 // logs an error (production) if you turn it off
    'caBundlePath'   => null,   // optional path to a custom CA bundle (PEM);
                                 // supplying one disables the bundled-CA fallback
    'timeout'        => 30,     // total request timeout (seconds)
    'connectTimeout' => 10,     // connection timeout (seconds)
]);
```

### Token caching

[](#token-caching)

`createCheckoutUrl()` and `getOrderStatus()` cache the fabric token until its `expirationDate` (minus a 60s safety margin) **within the client instance**and reuse it, saving a gateway round-trip whenever one request performs several calls (e.g. a settle path). A rejected token (HTTP 401) drops the cache automatically. Note PHP's request lifecycle: the cache does not persist across requests. Opt out for strictly stateless behavior:

```
$client = new Telebirr($config, null, null, ['cacheFabricToken' => false]);
```

`applyFabricToken()` always performs a real network call (and refreshes the cache), so existing manual flows are unaffected.

### Retrying transient gateway errors

[](#retrying-transient-gateway-errors)

The test gateway regularly throws transient infra errors (see the sandbox note below). Retry is **opt-in** with exponential backoff:

```
$client = new Telebirr($config, $logger, null, [
    'retry' => ['retries' => 2, 'delayMs' => 500, 'maxDelayMs' => 5000],
]);
```

Only failures where `ApiException::isTransient()` is true are retried: known Telebirr infra codes (`49401024991` "southbound service unavailable"), HTTP 502/503/504, and cURL timeouts/connection drops. Parameter or auth errors fail immediately. The code list is `ApiException::TRANSIENT_TELEBIRR_ERROR_CODES`.

### PSR-3 logging

[](#psr-3-logging)

```
use Monolog\Logger;

$log = new Logger('telebirr');
$client = new Telebirr($config, $log); // request/response logging (secrets & PII redacted)
```

### Injecting a custom HTTP client (testing)

[](#injecting-a-custom-http-client-testing)

The third constructor argument accepts any `Melaku\Telebirr\Http\HttpClientInterface`, so you can unit-test without hitting the network:

```
use Melaku\Telebirr\Http\HttpClientInterface;
use Melaku\Telebirr\Http\HttpResponse;

$fake = new class implements HttpClientInterface {
    public function post(string $url, array $headers, string $body): HttpResponse {
        return new HttpResponse(200, '{"token":"Bearer TEST"}');
    }
};

$client = new Telebirr($config, null, $fake);
```

### Catching errors

[](#catching-errors)

Every exception the library throws implements `Melaku\Telebirr\Exceptions\TelebirrExceptionInterface`, so you can catch them all in one place. API failures throw `ApiException`, which now carries Telebirr's parsed error envelope — no more `json_decode($e->getResponseBody())`:

```
use Melaku\Telebirr\Exceptions\ApiException;

try {
    $client->createCheckoutUrl('Order 123', '100.00');
} catch (ApiException $e) {
    $e->getHttpStatus();       // e.g. 400
    $e->getTelebirrCode();     // e.g. '49401024991' — parsed from the body
    $e->getTelebirrMessage();  // Telebirr's errorMsg
    $e->getTelebirrSolution(); // Telebirr's errorSolution remediation text
    $e->isTransient();         // true for retryable gateway-side failures
    $e->getResponseBody();     // raw body, if you need it
}
```

### Amounts &amp; rounding

[](#amounts--rounding)

`amount` accepts `string|int|float` and is formatted to exactly 2 decimals — Telebirr's wire format for ETB. If you store amounts in minor units (cents), divide before passing (`$cents / 100`). Prefer passing a **string**(`'100.50'`) when the value came from user input or a DB decimal column, sidestepping binary floating-point surprises.

### ⚠️ Sandbox instability

[](#️-sandbox-instability)

The **test gateway is frequently unstable** and returns transient infra errors that look exactly like integration bugs — most commonly:

```
errorCode 49401024991: "southbound business service is unavailable"

```

If your request worked before and suddenly throws a `4940…` code with an `errorSolution` suggesting a retry, **it's the gateway, not your code**. Wait and retry (or enable the `retry` option above). Don't spend an hour debugging a correct integration.

📚 Documentation
---------------

[](#-documentation)

For detailed documentation, API reference, and advanced usage examples, visit our documentation site:

**🔗 [Full Documentation](https://telebirr-php-docs.vercel.app)** *(Coming Soon)*

The documentation includes:

- Complete API reference
- Step-by-step integration guides
- Advanced configuration options
- Signature verification details
- Webhook/notification handling
- Error handling and troubleshooting
- Security best practices

🛠️ Helper Classes
-----------------

[](#️-helper-classes)

The library provides several helper classes to simplify common tasks:

- **`ReturnUrlHandler`** - Parse and verify return URL parameters
- **`NotificationHandler`** - Parse and verify payment notifications
- **`PaymentStatus`** - Check payment status values
- **`SignatureVerifier`** - Verify signatures from Telebirr

🔒 Security Notes
----------------

[](#-security-notes)

- Always verify signatures before processing payments
- Use HTTPS for all payment endpoints
- Store credentials in environment variables, not in code
- Implement idempotency checks for notifications
- Never trust return URL parameters alone - verify with server-to-server notifications

🤝 Contributing
--------------

[](#-contributing)

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License
---------

[](#-license)

This project is licensed under the MIT License.

🔗 Links
-------

[](#-links)

- [Telebirr Developer Portal](https://developer.ethiotelecom.et/)
- [Telebirr H5 C2B Integration Guide](https://developer.ethiotelecom.et/docs/H5%20C2B%20Web%20Payment%20Integration%20Quick%20Guide/requestCreateOrder)
- [Packagist](https://packagist.org/packages/melaku/telebirr)
- [GitHub Repository](https://github.com/MelakuDemeke/telebirr-php)

---

**Need help?** Check out the [full documentation](https://telebirr-php-docs.vercel.app) or open an issue on GitHub.

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance94

Actively maintained with recent releases

Popularity31

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~186 days

Recently: every ~43 days

Total

8

Last Release

32d ago

Major Versions

0.0.1a → v1.0.0a2022-12-20

v1.0.0a → v2.0.02026-01-23

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/38818398?v=4)[Melaku Demeke](/maintainers/MelakuDemeke)[@MelakuDemeke](https://github.com/MelakuDemeke)

---

Top Contributors

[![MelakuDemeke](https://avatars.githubusercontent.com/u/38818398?v=4)](https://github.com/MelakuDemeke "MelakuDemeke (68 commits)")

---

Tags

phptelebirrtelebirr-librarytelebirr-php

### Embed Badge

![Health badge](/badges/melaku-telebirr/health.svg)

```
[![Health](https://phpackages.com/badges/melaku-telebirr/health.svg)](https://phpackages.com/packages/melaku-telebirr)
```

###  Alternatives

[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

762297.9k53](/packages/civicrm-civicrm-core)[matomo/matomo

Matomo is the leading Free/Libre open analytics platform

21.7k39.6k](/packages/matomo-matomo)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M672](/packages/shopware-core)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[api-platform/metadata

API Resource-oriented metadata attributes and factories

275.5M254](/packages/api-platform-metadata)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
