aftandilmmd / odero-payment-for-laravel
OderoPay payment gateway integration for Laravel. A thin wrapper around aftandilmmd/odero-payment-for-php.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/aftandilmmd/odero-payment-for-laravel
Requires
- php: ^8.2
- aftandilmmd/odero-payment-for-php: @dev
- illuminate/log: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0|^4.0
This package is auto-updated.
Last update: 2026-02-09 11:18:11 UTC
README
English | Türkçe | Azərbaycanca
OderoPay for Laravel
Laravel wrapper for the OderoPay payment gateway. Supports both Azerbaijan (odero.az) and Turkey (oderopay.com.tr) regions with sandbox and live environments.
This package is a thin wrapper around odero-payment-for-php that adds Laravel-specific features: ServiceProvider auto-discovery, the Odero facade, config/odero.php with env variable support, and automatic Laravel Log channel integration.
Not using Laravel? See odero-payment-for-php -- the framework-agnostic core package that works with any PHP 8.2+ application. All DTOs, Enums, Exceptions, and API logic live there.
Requirements
- PHP 8.2+
- Laravel 11 or 12
Installation
composer require aftandilmmd/odero-payment-for-laravel
The service provider and facade are auto-discovered.
To publish the config file:
php artisan vendor:publish --tag=odero-config
Configuration
Add the following to your .env file:
ODERO_API_KEY=your-api-key ODERO_SECRET_KEY=your-secret-key ODERO_SANDBOX=true ODERO_REGION=az
Optional logging:
ODERO_LOG_ENABLED=true ODERO_LOG_CHANNEL=stack
Full config options in config/odero.php:
| Key | Description | Default |
|---|---|---|
api_key |
Merchant API key | '' |
secret_key |
Merchant secret key | '' |
sandbox |
Use sandbox environment | true |
region |
Region: az or tr |
az |
auth_version |
API auth version | V1 |
timeout |
HTTP timeout in seconds | 30 |
retry.times |
Retry attempts | 3 |
retry.sleep |
Retry delay in ms | 100 |
logging.enabled |
Enable request/response logging | false |
logging.channel |
Log channel | stack |
Usage
Use the Odero facade or inject OderoServiceInterface:
use Aftandilmmd\OderoPaymentLaravel\Facades\Odero; use Aftandilmmd\OderoPayment\Contracts\OderoServiceInterface; // Facade Odero::initCheckout([...]); // Dependency injection public function __construct(private OderoServiceInterface $odero) {} $this->odero->initCheckout([...]);
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" return redirect($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 completed $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), ]);
Storing the Card
To save the card for future payments, set storeCardAfterSuccessPayment to true in the card data. The response will include cardUserKey and cardToken for subsequent payments:
$payment = Odero::createPayment([ 'price' => 50.0, 'paidPrice' => 50.0, 'currency' => 'AZN', 'card' => [ 'cardHolderName' => 'John Doe', 'cardNumber' => '4508034508034509', 'expireYear' => '2030', 'expireMonth' => '12', 'cvc' => '000', 'storeCardAfterSuccessPayment' => true, 'cardAlias' => 'My Visa Card', ], 'items' => [['name' => 'Product', 'price' => 50.0]], ]); // Save these for future payments $cardUserKey = $payment->cardUserKey; $cardToken = $payment->cardToken;
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 return response($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:
// Create payment with PRE_AUTH phase $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 a specific transaction within a payment:
$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']}"; // "My Visa Card: 450803****4509" }
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)"; } // "1x 100.0 AZN (total: 100.0 AZN)" // "3x 34.5 AZN (total: 103.5 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.
Architecture
This package is a thin wrapper around odero-payment-for-php. It only contains:
OderoServiceProvider-- BindsOderoServiceInterfaceas a singleton usingOderoFactory::create(), merges config, and bridges Laravel's Log channel as a PSR-3 logger.OderoFacade -- Proxies toOderoServiceInterfacefor static method calls.config/odero.php-- Laravel config file with env variable support.
All DTOs, Enums, Exceptions, Contracts, Services, and business logic come from the core PHP package.
Testing
php artisan test --testsuite=Odero
22 tests, 76 assertions. Uses Guzzle MockHandler with JSON fixtures via the fakeOdero() test helper.
Related Packages
- odero-payment-for-php -- The framework-agnostic core package. Contains all DTOs, Enums, Exceptions, Services, and API logic. Use this directly in non-Laravel PHP applications, or when you need full control over the HTTP client and logger.
License
MIT