aftandilmmd/odero-payment-for-php

OderoPay payment gateway integration for PHP.

Installs: 0

Dependents: 1

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/aftandilmmd/odero-payment-for-php

dev-main 2026-02-09 11:17 UTC

This package is auto-updated.

Last update: 2026-02-09 11:18:25 UTC


README

English | Türkçe | Azərbaycanca

OderoPay for PHP

Framework-agnostic OderoPay payment gateway integration for PHP. Supports both Azerbaijan (odero.az) and Turkey (oderopay.com.tr) regions with sandbox and live environments.

Works with any PHP 8.2+ application -- no Laravel or other framework required.

Using Laravel? See odero-payment-for-laravel -- a thin wrapper that adds auto-discovery, config/odero.php, the Odero facade, and Laravel Log channel integration. All DTOs, Enums, Exceptions, and core logic come from this package.

Requirements

  • PHP 8.2+
  • Guzzle HTTP 7.0+

Installation

composer require aftandilmmd/odero-payment-for-php

Quick Start

use Aftandilmmd\OderoPayment\OderoFactory;

$odero = OderoFactory::create([
    'api_key' => 'your-api-key',
    'secret_key' => 'your-secret-key',
    'sandbox' => true,
    'region' => 'az', // 'az' or 'tr'
]);

$payment = $odero->createPayment([
    'price' => 100.0,
    'paidPrice' => 100.0,
    'currency' => 'AZN',
    'paymentGroup' => 'PRODUCT',
    'card' => [
        'cardHolderName' => 'John Doe',
        'cardNumber' => '4508034508034509',
        'expireYear' => '2030',
        'expireMonth' => '12',
        'cvc' => '000',
    ],
    'items' => [['name' => 'Product', 'price' => 100.0]],
]);

echo $payment->paymentStatus; // "SUCCESS"

Configuration

$odero = OderoFactory::create([
    'api_key' => 'your-api-key',      // Required
    'secret_key' => 'your-secret-key', // Required
    'sandbox' => true,                  // Default: true
    'region' => 'az',                   // Default: 'az' ('az' or 'tr')
    'auth_version' => 'V1',            // Default: 'V1'
    'timeout' => 30,                    // Default: 30 seconds
    'retry' => [
        'times' => 3,                  // Default: 3
        'sleep' => 100,                // Default: 100ms
    ],
    'logging' => [
        'enabled' => false,            // Default: false
    ],
]);

Custom Guzzle Client

You can pass your own Guzzle client instance:

use GuzzleHttp\Client;

$client = new Client(['timeout' => 60, 'proxy' => 'http://proxy:8080']);
$odero = OderoFactory::create($config, $client);

PSR-3 Logger

Pass any PSR-3 compatible logger (Monolog, etc.) and enable logging:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('odero');
$logger->pushHandler(new StreamHandler('path/to/odero.log'));

$odero = OderoFactory::create([
    'api_key' => 'your-key',
    'secret_key' => 'your-secret',
    'logging' => ['enabled' => true],
], logger: $logger);

API Reference

Checkout Payments

Initialize a checkout session and redirect the user to OderoPay's hosted payment page.

$response = $odero->initCheckout([
    'price' => 100.0,
    'paidPrice' => 100.0,
    'currency' => 'AZN',
    'paymentGroup' => 'PRODUCT',
    'conversationId' => 'order-123',
    'callbackUrl' => 'https://example.com/payment/callback',
    'items' => [
        ['name' => 'Monthly subscription', 'price' => 100.0],
    ],
]);

$response->token;          // "ckm-token-abc"
$response->pageUrl;        // "https://payment.odero.az/ckm-token-abc"
$response->tokenExpireDate; // "2026-03-01T12:00:00"

// Redirect user to $response->pageUrl

Retrieve the payment result after the callback:

$payment = $odero->retrieveCheckout($token);

$payment->id;             // 12345
$payment->paymentStatus;  // "SUCCESS"
$payment->price;          // 100.0
$payment->cardType;       // "CREDIT_CARD"

Card Payments (Non-3DS)

Charge a card directly without 3D Secure:

$payment = $odero->createPayment([
    'price' => 50.0,
    'paidPrice' => 50.0,
    'currency' => 'AZN',
    'paymentGroup' => 'PRODUCT',
    'conversationId' => 'order-456',
    'card' => [
        'cardHolderName' => 'John Doe',
        'cardNumber' => '4508034508034509',
        'expireYear' => '2030',
        'expireMonth' => '12',
        'cvc' => '000',
    ],
    'items' => [
        ['name' => 'Product A', 'price' => 30.0],
        ['name' => 'Product B', 'price' => 20.0],
    ],
]);

if ($payment->paymentStatus === 'SUCCESS') {
    $payment->id;              // Payment ID
    $payment->binNumber;       // "450803"
    $payment->lastFourDigits;  // "4509"
    $payment->cardAssociation; // "VISA"
}

Using the Card DTO

use Aftandilmmd\OderoPayment\DTOs\Card;
use Aftandilmmd\OderoPayment\DTOs\PaymentItem;

$card = new Card(
    cardHolderName: 'John Doe',
    cardNumber: '4508034508034509',
    expireYear: '2030',
    expireMonth: '12',
    cvc: '000',
    storeCardAfterSuccessPayment: true,
    cardAlias: 'My Visa',
);

$items = [
    new PaymentItem(name: 'Product A', price: 30.0),
    new PaymentItem(name: 'Product B', price: 20.0, externalId: 'SKU-001'),
];

$payment = $odero->createPayment([
    'price' => 50.0,
    'paidPrice' => 50.0,
    'currency' => 'AZN',
    'card' => $card->toArray(),
    'items' => array_map(fn ($item) => $item->toArray(), $items),
]);

Paying with a Stored Card

$payment = $odero->createPayment([
    'price' => 50.0,
    'paidPrice' => 50.0,
    'currency' => 'AZN',
    'card' => [
        'cardUserKey' => $savedCardUserKey,
        'cardToken' => $savedCardToken,
    ],
    'items' => [['name' => 'Product', 'price' => 50.0]],
]);

3D Secure Payments

Step 1: Initialize

$response = $odero->init3dsPayment([
    'price' => 100.0,
    'paidPrice' => 100.0,
    'currency' => 'AZN',
    'paymentGroup' => 'PRODUCT',
    'conversationId' => 'order-789',
    'callbackUrl' => 'https://example.com/payment/3ds-callback',
    'card' => [
        'cardHolderName' => 'John Doe',
        'cardNumber' => '4508034508034509',
        'expireYear' => '2030',
        'expireMonth' => '12',
        'cvc' => '000',
    ],
    'items' => [['name' => 'Product', 'price' => 100.0]],
]);

// Render the 3DS form in the browser
echo $response->decodedHtml();

Step 2: Complete (in callback handler)

$payment = $odero->complete3dsPayment($paymentId);

if ($payment->paymentStatus === 'SUCCESS') {
    // 3DS payment completed
}

Pre-Authorization

Create a pre-authorized payment (hold funds) and capture later:

$payment = $odero->createPayment([
    'price' => 200.0,
    'paidPrice' => 200.0,
    'currency' => 'AZN',
    'paymentGroup' => 'PRODUCT',
    'paymentPhase' => 'PRE_AUTH',
    'card' => [...],
    'items' => [['name' => 'Hotel booking', 'price' => 200.0]],
]);

// Later, capture the funds (full or partial amount)
$captured = $odero->postAuthPayment($payment->id, 200.0);

Refunds

Full Refund

$refund = $odero->refund(paymentId: 12345);

$refund->id;                    // Refund ID
$refund->status;                // "SUCCESS"
$refund->refundPrice;           // 100.0
$refund->refundType;            // "REFUND"
$refund->refundDestinationType; // "CARD"

With optional parameters:

$refund = $odero->refund(
    paymentId: 12345,
    conversationId: 'refund-001',
    destinationType: 'CARD',  // or 'WALLET'
);

Partial Refund

$refund = $odero->partialRefund(
    paymentTransactionId: 99,
    refundPrice: 30.0,
    conversationId: 'partial-refund-001',
);

$refund->id;                   // Partial refund ID
$refund->refundPrice;          // 30.0
$refund->isAfterSettlement;    // false
$refund->paymentTransactionId; // 99

Retrieve Refund Status

$refund = $odero->retrieveRefund(refundId: 5001);
$partialRefund = $odero->retrievePartialRefund(id: 7001);

Payment Reporting

Retrieve a Single Payment

$report = $odero->retrievePayment(paymentId: 12345);

$report->id;
$report->orderId;
$report->paymentStatus;
$report->paymentCard;         // Card details array
$report->paymentRefunds;      // Refunds array
$report->paymentTransactions; // Transactions array

Search Payments

$results = $odero->searchPayments([
    'paymentStatus' => 'SUCCESS',
    'currency' => 'AZN',
    'minPrice' => 10.0,
    'maxPrice' => 500.0,
    'page' => 0,
    'size' => 20,
]);

$results->items;     // Array of payments
$results->totalSize; // Total matching payments
$results->size;      // Page size

foreach ($results->items as $payment) {
    echo "{$payment['orderId']}: {$payment['price']} AZN";
}

Subscriptions

Retrieve a Subscription

$sub = $odero->retrieveSubscription(8001);

$sub->id;              // 8001
$sub->price;           // "29.99"
$sub->periodType;      // "30" (days)
$sub->status;          // 0 (Active)
$sub->nextPaymentDate; // "2026-02-15"
$sub->retryCount;      // 0

Search Subscriptions

$results = $odero->searchSubscriptions([
    'page' => 0,
    'size' => 10,
]);

Update Subscription Status

use Aftandilmmd\OderoPayment\Enums\SubscriptionStatus;

// Deactivate
$odero->updateSubscriptionStatus(8001, SubscriptionStatus::Passive->value);

// Reactivate
$odero->updateSubscriptionStatus(8001, SubscriptionStatus::Active->value);

Subscription Plan DTO

use Aftandilmmd\OderoPayment\DTOs\SubscriptionPlan;

$plan = new SubscriptionPlan(
    price: 29.99,
    periodType: 30,       // 7 = weekly, 30 = monthly, 360 = yearly
    nochargeDayCount: 7,  // Free trial days
    description: 'Monthly Plan',
);

// Use $plan->toArray() when creating subscriptions

Pay by Link

Create shareable payment links with optional QR codes.

Create

$link = $odero->createPayByLink([
    'name' => 'Premium subscription',
    'price' => 49.99,
    'currency' => 'AZN',
    'stock' => 100,
    'enabledInstallments' => '1,2,3,6',
    'expireDate' => '2026-12-31T23:59:59',
]);

$link->id;        // 9001
$link->status;    // "ACTIVE"
$link->token;     // "pbl-token-abc"
$link->url;       // Payment URL
$link->qrCodeUrl; // QR code image URL
$link->soldCount; // 0

Update

$link = $odero->updatePayByLink(9001, [
    'name' => 'Updated product name',
    'price' => 59.99,
    'stock' => 50,
]);

Retrieve

$link = $odero->retrievePayByLink(9001);

Card Storage

Search Stored Cards

$cards = $odero->searchStoredCards([
    'cardUserKey' => 'card-user-key-abc',
]);

foreach ($cards->items as $card) {
    echo "{$card['cardAlias']}: {$card['binNumber']}****{$card['lastFourDigits']}";
}

Delete a Stored Card

$odero->deleteStoredCard([
    'cardUserKey' => 'card-user-key-abc',
    'cardToken' => 'card-token-xyz',
]);

BIN Lookup & Installments

BIN Lookup

Get card and bank info from the first 6 digits:

$bin = $odero->lookupBin('450803');

$bin->binNumber;       // "450803"
$bin->cardType;        // "CREDIT_CARD"
$bin->cardAssociation; // "VISA"
$bin->cardBrand;       // "Bonus"
$bin->bankName;        // "Kapital Bank"
$bin->bankCode;        // 101
$bin->commercial;      // false

Get Installment Options

// All banks
$installments = $odero->getInstallments(100.0, 'AZN');

// Specific bank (by BIN)
$installments = $odero->getInstallments(100.0, 'AZN', '450803');

$installments->binNumber;         // "450803"
$installments->bankName;          // "Kapital Bank"
$installments->installmentPrices; // Array of installment options

foreach ($installments->installmentPrices as $option) {
    $count = $option['installmentNumber'];
    $total = $option['totalPrice'];
    $monthly = $option['installmentPrice'];
    echo "{$count}x {$monthly} AZN (total: {$total} AZN)";
}

Buyers

Manage buyer records for saved customer data.

Create

$buyer = $odero->createBuyer([
    'name' => 'John',
    'surname' => 'Doe',
    'email' => 'john@example.com',
    'gsmNumber' => '+994501234567',
    'identityNumber' => 'ABC123',
    'registrationAddress' => 'Baku, Azerbaijan',
    'city' => 'Baku',
    'country' => 'Azerbaijan',
]);

$buyer->id;     // 2001
$buyer->status; // "ACTIVE"

Update

$buyer = $odero->updateBuyer(2001, [
    'name' => 'Jane',
    'email' => 'jane@example.com',
]);

Retrieve

$buyer = $odero->retrieveBuyer(2001);

Payment Transactions

Approve or reject payment transactions for settlement.

Approve

$odero->approveTransactions([99, 100, 101]);

// Transactional mode: all-or-nothing
$odero->approveTransactions([99, 100], isTransactional: true);

Disapprove

$odero->disapproveTransactions([99, 100]);

Error Handling

All API errors throw typed exceptions:

use Aftandilmmd\OderoPayment\Exceptions\OderoException;
use Aftandilmmd\OderoPayment\Exceptions\OderoAuthenticationException;

try {
    $payment = $odero->createPayment([...]);
} catch (OderoAuthenticationException $e) {
    // 401 - Invalid API credentials
    // $e->getMessage()  => "OderoPay authentication failed."
    // $e->getCode()     => 401
} catch (OderoException $e) {
    // 4xx/5xx - API error
    // $e->getMessage()  => "Invalid card number"
    // $e->getCode()     => 400
    // $e->errors        => ['errorCode' => '10051', 'errorDescription' => '...', 'errorGroup' => 'VALIDATION']
}

Response Helper Methods

All response DTOs extend OderoResponse and include:

$response->isSuccessful(); // true if no errors
$response->hasErrors();    // true if errors present
$response->rawData;        // Full raw API response array
$response->errors;         // Errors array (if any)

Enums

The package provides typed enums for all API constants:

use Aftandilmmd\OderoPayment\Enums\Currency;
use Aftandilmmd\OderoPayment\Enums\PaymentStatus;
use Aftandilmmd\OderoPayment\Enums\SubscriptionStatus;

// Values
Currency::Azn->value;              // "AZN"
PaymentStatus::Success->value;     // "SUCCESS"
SubscriptionStatus::Active->value; // 0

// Labels
Currency::Azn->label();            // "Azerbaycan Manati"
PaymentStatus::Success->label();   // "Basarili"

// Select options (value => label)
Currency::options();
// ["AZN" => "Azerbaycan Manati", "TRY" => "Turk Lirasi", ...]

Available enums: Currency, PaymentGroup, PaymentPhase, PaymentStatus, CardType, CardAssociation, RefundType, RefundDestinationType, SubscriptionStatus, TransactionStatus.

Testing

vendor/bin/pest

107 tests, 353 assertions. Uses Guzzle MockHandler with JSON fixtures.

Related Packages

  • odero-payment-for-laravel -- Laravel wrapper that adds ServiceProvider auto-discovery, the Odero facade, config/odero.php with env variable support, and automatic Laravel Log channel integration. Requires this package as a dependency.

License

MIT