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

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

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 -- Binds OderoServiceInterface as a singleton using OderoFactory::create(), merges config, and bridges Laravel's Log channel as a PSR-3 logger.
  • Odero Facade -- Proxies to OderoServiceInterface for 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