Skip to content

Gateway: Zibal ​

Current version: v0.0.11

telegram-bot-essentials/gateway-zibal (namespace TelegramBotEssentials\GatewayZibal) integrates Zibal — an Iranian online payment gateway — with Billing. Unlike Gateway: Card, this is a fully automated, redirect-based gateway: the buyer is sent to a real payment page and the invoice is marked paid automatically via a server-to-server callback.

Installation ​

bash
composer require telegram-bot-essentials/gateway-zibal
php artisan migrate

Configure per-bot via Settings:

Setting keyTypePurpose
billing.gateways.zibal.statusCHECKBOXMaster on/off switch
billing.gateways.zibal.merchantSENSITIVEZibal merchant key (encrypted at rest)

Both must be set for the gateway to appear on the invoice screen.

Flow ​

Because Zibal payment happens on Zibal's own web page (outside Telegram), this gateway is driven by HTTP routes, not CallbackQueries/StateAnswers — it registers none of either.

Invoice screen (Billing)
      │  inline button → external URL: route('invoice.zibal.pay', $invoice->public_token)
      ▼
GatewayZibalController::pay($token)
      │  converts invoice price to Rial: priceIn($invoice->price)->toIRR()
      │  zibal()->paymentRequest($amountRial, callbackUrl)->execute()
      │  creates a ToZibalAttempt with the returned trackId
      │  billing()->attemptPayment($invoice, $attempt)
      ▼
redirect → https://gateway.zibal.ir/start/{trackId}   (buyer pays on Zibal's page)
      ▼
Zibal redirects back to:
GatewayZibalController::callback($token)
      │  zibal()->verify($trackId)->execute()
      │  on success: $zibalAttempt->attemptSucceed() → Invoice::markAsPaid()
      │  renders a result Blade view with a "back to bot" deep link (?start=invoice_{id})

Because this runs outside any Telegram webhook request, the controller manually re-establishes tenancy and the webhook context before touching wHook() or billing/invoice state:

php
tenancy()->initialize($invoice->bot);
wHook()->setBot($invoice->bot);
wHook()->setApi(new Api($invoice->bot->bot_token));
wHook()->setUser($invoice->botUser);
wHook()->setUpdate(Update::make(request()->all()));

This is the pattern to copy for any gateway or webhook that arrives via a plain HTTP route instead of the Telegram webhook.

The Zibal service ​

Helper: zibal(). Thin wrapper exposing Zibal's two API calls as fluent method-object pairs (src/Services/Methods/PaymentRequest.php, Verify.php):

php
zibal()->paymentRequest(int $amountRial, string $callbackUrl)->execute();
// => ['result' => 100, 'trackId' => ..., 'message' => ...]

zibal()->verify(string $trackId)->execute();
// => ['status' => 1, 'amount' => ..., 'message' => ...]

result === 100 signals a successful payment request; status === 1 signals a successful verification. Both are checked explicitly in the controller — don't assume a non-error HTTP response means success.

ToZibalAttempt ​

Extends Billing's abstract PaymentAttempt. Columns: track_id, amount, received_amount. Both lifecycle hooks are currently no-ops (attemptSucceedHook/attemptFailedHook) — Invoice::markAsPaid()/markAsFailed() (triggered by attemptSucceed()/attemptFailed()) already does the work that matters.

Registering with Billing ​

php
gateways()->addGateway(new Gateway(
    key: 'zibal',
    label: 'Zibal',
    inlineButtonGenerator: function (Invoice $invoice) {
        if (!settings()->get('billing.gateways.zibal.status') || !settings()->get('billing.gateways.zibal.merchant')) {
            return null;
        }
        return Keyboard::inlineButton([
            'text' => __('tbe-billing::invoice.summary.keys.to_zibal', [
                'price' => number_format(priceIn($invoice->price)->toIRT()),
            ]),
            'url' => route('invoice.zibal.pay', ['token' => $invoice->public_token]),
        ]);
    },
));

Note this button uses 'url' (an external link), not 'callback_data' — appropriate for a gateway whose flow leaves Telegram entirely, in contrast to Gateway: Card's callback_data button.

Comparing the two gateways ​

Gateway: CardGateway: Zibal
Payment happensInside the Telegram chatOn an external web page
VerificationManual (admin accept/reject)Automatic (verify() callback)
Button typecallback_dataurl
Uses CallbackQuery/StateAnswerYesNo — HTTP routes/controller instead
Sensitive credentialsNoneMerchant key (SENSITIVE setting)

If you're integrating another redirect-based provider (an alternative to Zibal), this package — not Gateway: Card — is the template to follow.