mattdi/telebirr-ussd

Laravel integration package for Telebirr SOAP payment APIs, including asynchronous USSD Push / Buy Goods.

Maintainers

Package info

github.com/Matt-di/telebir-ussd

pkg:composer/mattdi/telebirr-ussd

Transparency log

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.3.0 2026-08-29 11:06 UTC

This package is auto-updated.

Last update: 2026-08-29 11:36:27 UTC


README

v0.3.1 (Laravel 10/11/12/13 · PHP ^8.1) · A Laravel package for the asynchronous USSD Push / Buy Goods for Customer operation of the Telebirr SOAP payment API.

⚠️ Important: The Telebirr USSD Push flow is asynchronous. The immediate response only confirms that Telebirr accepted your request — it is not a payment confirmation. Only the api:Result callback with ResultCode = 0 confirms a successful payment. Treat callback data as the source of truth.

Features

  • InitTrans_BuyGoodsForCustomer request builder (Buy Goods / USSD Push)
  • SOAP/XML request serialization and response parsing
  • Initial synchronous acceptance response handling
  • Asynchronous api:Result callback parsing
  • TelebirrTransactionCompleted event for async settlement
  • Laravel service provider + Telebirr facade
  • Configurable endpoint, credentials, timeouts, and TLS/mTLS options
  • Callback idempotency hook (IdempotencyChecker)
  • Injectable security credential provider
  • PHPUnit tests with Telebirr XML fixtures
  • No undocumented Telebirr cryptographic algorithm hard-coded

Installation

Require the package:

composer require mattdi/telebirr-ussd

Publish the config file:

php artisan vendor:publish --tag=telebirr-ussd-config

Environment variables

Add the following to your .env:

# SOAP endpoint supplied by Telebirr
TELEBIRR_USSD_ENDPOINT=

# Publicly reachable URL Telebirr sends the async api:Result callback to
TELEBIRR_USSD_RESULT_URL=https://your-domain.example/api/telebirr/callback

# --- Header/Caller ---  authenticates your application as the API consumer
TELEBIRR_USSD_THIRD_PARTY_ID=
TELEBIRR_USSD_PASSWORD=            # Caller/Password  (provided by Telebirr)
TELEBIRR_USSD_KEY_OWNER=1
TELEBIRR_USSD_CALLER_TYPE=2

# --- Identity/Initiator ---  authenticates the merchant initiating the payment
TELEBIRR_USSD_INITIATOR_IDENTIFIER=        # Identifier
TELEBIRR_USSD_INITIATOR_IDENTIFIER_TYPE=12
TELEBIRR_USSD_SECURITY_CREDENTIAL=         # Initiator/SecurityCredential (provided by Telebirr)
TELEBIRR_USSD_INITIATOR_SHORT_CODE=        # ShortCode

# --- TLS / mTLS (production) ---
TELEBIRR_USSD_VERIFY_TLS=true
TELEBIRR_USSD_CERTIFICATE=
TELEBIRR_USSD_CERTIFICATE_TYPE=PEM
TELEBIRR_USSD_PRIVATE_KEY=
TELEBIRR_USSD_PRIVATE_KEY_TYPE=PEM
TELEBIRR_USSD_PRIVATE_KEY_PASSPHRASE=
TELEBIRR_USSD_CA=

# --- Optional tuning ---  (defaults shown)
TELEBIRR_USSD_CONNECT_TIMEOUT=10
TELEBIRR_USSD_TIMEOUT=30
TELEBIRR_USSD_COMMAND_ID=InitTrans_BuyGoodsForCustomer
TELEBIRR_USSD_SOAP_ACTION=InitTrans_BuyGoodsForCustomer

Every variable maps directly to a field in the outlined Telebirr spec, so there are no redundant settings — each one corresponds to a different part of the request (Caller, Initiator) or the transport (SOAP timeout/mTLS).

Usage

Initiate a USSD Push / Buy Goods

use Mattdi\Telebirr\Facades\Telebirr;
use Mattdi\Telebirr\DTOs\BuyGoodsPayment;

$response = Telebirr::buyGoods(new BuyGoodsPayment(
    customerMsisdn: '2519XXXXXXXX',   // customer phone (no leading +)
    merchantShortCode: '20088001',    // merchant shortcode
    amount: '10.00',
    currency: 'ETB',
));

if ($response->accepted()) {
    // Request accepted for asynchronous processing.
    // Do NOT mark the order as paid yet. Await the callback.
}

ResponseCode = 0 in the immediate response means the request was accepted, not that payment succeeded.

Handle the callback

By default the package registers:

POST /telebirr/callback

Point Telebirr's ResultURL at the publicly reachable URL configured via TELEBIRR_USSD_RESULT_URL.

The callback parses:

  • ResultType
  • ResultCode
  • ResultDesc
  • OriginatorConversationID
  • ConversationID
  • TransactionID

A successful result has ResultCode = 0. Listen for the completion event to settle the payment:

use Mattdi\Telebirr\Events\TelebirrTransactionCompleted;

Event::listen(TelebirrTransactionCompleted::class, function ($event) {
    $result = $event->result;

    // Find your payment using OriginatorConversationID.
    // Verify business context and idempotency.
    // Mark the payment/order successful only after your validation.
});

Recommended payment lifecycle

Treat the immediate API response and the async callback as separate events:

CREATED
  -> PENDING / REQUEST_ACCEPTED      (immediate response: accepted)
  -> SUCCESS                          (callback ResultCode = 0)
  -> FAILED                           (callback error or missing)

Persist at least:

  • your internal payment UUID
  • originator_conversation_id
  • Telebirr conversation_id
  • Telebirr transaction_id
  • amount / currency
  • customer MSISDN
  • merchant shortcode
  • result code / description
  • timestamps
  • processing status

Idempotency

The package intentionally does not require a database schema. In your listener, use originatorConversationId, conversationId, and especially transactionId as idempotency keys before applying fulfillment logic.

You can also bind your own IdempotencyChecker. The default implementation is a no-op:

use Mattdi\Telebirr\Webhooks\IdempotencyChecker;

app()->bind(IdempotencyChecker::class, YourIdempotencyChecker::class);

Credentials

The two credential strings shown in the spec's example request are provided by Telebirr when you onboard:

  • Caller/PasswordTELEBIRR_USSD_PASSWORD
  • Initiator/SecurityCredentialTELEBIRR_USSD_SECURITY_CREDENTIAL

Both are sent verbatim into the SOAP request, so you can paste them straight into your .env.

Security credential provider

The package reads the credential via a small SecurityCredentialProvider contract; the default implementation returns TELEBIRR_USSD_SECURITY_CREDENTIAL. It exists so that, if Telebirr later publishes a getSecurityCredential() algorithm, you can compute the value at runtime instead of storing it statically:

use Mattdi\Telebirr\Security\SecurityCredentialProvider;

app()->bind(SecurityCredentialProvider::class, function () {
    return new YourProvider();
});

Security & production hardening

Before going to production:

  1. Obtain the official Telebirr credential/security specification.
  2. Use the official production SOAP endpoint.
  3. Use the official production SOAP endpoint over HTTPS; enable mTLS client certificates only if Telebirr requires them.
  4. Restrict and authenticate the callback endpoint per Telebirr's requirements.
  5. Add idempotency using the Telebirr transaction/conversation identifiers.
  6. Never log PINs, security credentials, private keys, or raw authentication secrets.
  7. Verify the payment amount/order before fulfilling goods.
  8. Implement reconciliation/query handling if Telebirr provides it.
  9. Add monitoring and structured audit logs.

Architecture

Laravel application
       |
       v
Telebirr facade/service
       |
       +--> BuyGoodsRequestBuilder
       |       |
       |       +--> SOAP XML
       |
       +--> TelebirrClient
       |       |
       |       +--> Telebirr SOAP endpoint
       |
       +--> InitTransactionResponseParser
       |
       +--> ResultHandler
               |
               +--> ResultParser
               +--> TelebirrTransactionCompleted event

Changelog

See CHANGELOG.md for version history.

License

MIT