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
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- psr/log: ^3.0
Requires (Dev)
- pestphp/pest: ^3.0|^4.0
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, theOderofacade, 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
Oderofacade,config/odero.phpwith env variable support, and automatic Laravel Log channel integration. Requires this package as a dependency.
License
MIT