mattdi / telebirr-ussd
Laravel integration package for Telebirr SOAP payment APIs, including asynchronous USSD Push / Buy Goods.
Requires
- php: ^8.1
- ext-dom: *
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- psr/log: ^3.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.0|^11.0
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:Resultcallback withResultCode = 0confirms a successful payment. Treat callback data as the source of truth.
Features
InitTrans_BuyGoodsForCustomerrequest builder (Buy Goods / USSD Push)- SOAP/XML request serialization and response parsing
- Initial synchronous acceptance response handling
- Asynchronous
api:Resultcallback parsing TelebirrTransactionCompletedevent for async settlement- Laravel service provider +
Telebirrfacade - 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:
ResultTypeResultCodeResultDescOriginatorConversationIDConversationIDTransactionID
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/Password→TELEBIRR_USSD_PASSWORDInitiator/SecurityCredential→TELEBIRR_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:
- Obtain the official Telebirr credential/security specification.
- Use the official production SOAP endpoint.
- Use the official production SOAP endpoint over HTTPS; enable mTLS client certificates only if Telebirr requires them.
- Restrict and authenticate the callback endpoint per Telebirr's requirements.
- Add idempotency using the Telebirr transaction/conversation identifiers.
- Never log PINs, security credentials, private keys, or raw authentication secrets.
- Verify the payment amount/order before fulfilling goods.
- Implement reconciliation/query handling if Telebirr provides it.
- 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