mskayali/halkode

Halkode Payment Gateway SDK for Yii2

Maintainers

Package info

github.com/mskayali/halkode

Homepage

Type:yii2-extension

pkg:composer/mskayali/halkode

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-03-04 15:29 UTC

This package is auto-updated.

Last update: 2026-03-04 15:34:18 UTC


README

A PHP SDK for integrating the Halkode payment gateway into your application. Designed as a Yii2-compatible Composer package with clean abstraction for token storage, encryption, and multi-language response handling.

πŸ“– Official Integration Docs: This SDK is built based on the official Halkode Integration Documentation. Refer to it for detailed API specifications, parameter descriptions, and response formats.

Table of Contents

Requirements

Installation

composer require mskayali/halkode

Configuration

As a Yii2 Component

Add the following to your config/web.php or config/main.php:

'components' => [
    'halkode' => [
        'class'            => \mskayali\halkode\Client::class,
        'baseUrl'          => \mskayali\halkode\Client::BASE_URL_DEV, // or BASE_URL_PROD
        'merchantUID'      => 'your-merchant-uid',
        'apikeypublic'     => 'your-api-secret-key',
        'apiclientpublic'  => 'your-api-client-key',
        'language'         => 'tr', // 'tr' or 'en' for response messages
        // Optional: custom token storage (see Token Storage section)
        // 'tokenStorage' => new RedisTokenStorage($redis),
    ],
],

Standalone Usage

use mskayali\halkode\Client;

$client = new Client([
    'baseUrl'          => Client::BASE_URL_DEV,
    'merchantUID'      => 'your-merchant-uid',
    'apikeypublic'     => 'your-api-secret-key',
    'apiclientpublic'  => 'your-api-client-key',
]);

$services = $client->getPaymentServices();

Environment URLs

Environment Constant URL
Development Client::BASE_URL_DEV https://testapp.halkode.com.tr/ccpayment
Production Client::BASE_URL_PROD https://app.halkode.com.tr/ccpayment

Quick Start

use mskayali\halkode\Client;
use mskayali\halkode\models\PurchaseLinkRequest;
use mskayali\halkode\models\Invoice;
use mskayali\halkode\models\InvoiceItem;

// 1. Create client
$client = new Client([
    'baseUrl'          => Client::BASE_URL_DEV,
    'merchantUID'      => 'your-merchant-uid',
    'apikeypublic'     => 'your-api-secret-key',
    'apiclientpublic'  => 'your-api-client-key',
]);

// 2. Build invoice
$invoice = new Invoice([
    'invoice_id'          => 'INV-' . time(),
    'invoice_description' => 'Order #123',
    'total'               => 250.00,
    'return_url'          => 'https://example.com/payment/success',
    'cancel_url'          => 'https://example.com/payment/cancel',
    'items'               => [
        new InvoiceItem(['name' => 'Widget', 'price' => 250, 'quantity' => 1]),
    ],
]);

// 3. Create payment link
$services = $client->getPaymentServices();
$result   = $services->purchaseLink(new PurchaseLinkRequest([
    'currency_code' => 'TRY',
    'invoice'       => $invoice,
    'name'          => 'John',
    'surname'       => 'Doe',
]));

if ($result->success) {
    // Redirect user to the payment page
    header('Location: ' . $result->data->link);
}

API Reference

Available Services

All services are accessed through $client->getPaymentServices():

Method Endpoint Description
purchaseLink($request) /purchase/link Generate a hosted payment page link
paySmart2D($request) /api/paySmart2D Direct card payment (no 3D Secure)
paySmart3D($request) /api/paySmart3D Card payment with 3D Secure verification
refundPayment($request) /api/refund Full or partial refund
checkstatus($request) /api/checkstatus Query transaction status
confirmPayment($request) /api/confirmPayment Confirm a pre-authorized payment
getInstallment($request) /api/installments Query available installment options
merchantCommissions($request) /api/commissions Retrieve merchant commission rates
getPos($request) /api/getpos Query available POS terminals for a card
getTransaction($request) /api/getTransactions Retrieve a single transaction
allTransaction($request) /api/alltransaction List all transactions
paymentComplete($request) /payment/complete Finalize a payment

Response Structure

Every service method returns an object with these attributes:

$result->success;  // bool β€” whether the operation succeeded
$result->message;  // string|null β€” localized status message (based on Client language)
$result->data;     // object|null β€” response data on success
$result->error;    // mixed|null β€” error details on failure

Payment Workflows

Workflow 1: Hosted Payment Page (Purchase Link)

Best for: redirecting users to a Halkode-hosted payment page.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Client   │────▢│  Server  │────▢│ Halkode  │────▢│ Customer β”‚
β”‚  (Your    β”‚     β”‚ purchase β”‚     β”‚ Hosted   β”‚     β”‚ completesβ”‚
β”‚   App)    │◀────│  Link()  │◀────│ Page     │◀────│ payment  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                       β”‚
                               redirect to return_url
                               or cancel_url
// Step 1: Create payment link
$result = $services->purchaseLink(new PurchaseLinkRequest([
    'currency_code' => 'TRY',
    'invoice'       => $invoice,
    'name'          => 'John',
    'surname'       => 'Doe',
]));

// Step 2: Redirect user
if ($result->success) {
    return redirect($result->data->link);
}

// Step 3: Handle callback (on your return_url)
// Halkode redirects back with payment result in POST data
$invoiceId = $_POST['invoice_id'];
$status    = $services->checkstatus(new CheckstatusRequest([
    'invoice_id'   => $invoiceId,
    'merchant_key' => $client->getMerchantUID(),
]));

if ($status->success) {
    // Payment confirmed β€” update your order
}

Workflow 2: Direct Card Payment (2D)

Best for: collecting card data on your own PCI-compliant form.

// Step 1: Charge the card directly
$result = $services->paySmart2D(new PaySmart2DRequest([
    'cc_holder_name'      => 'John Doe',
    'cc_no'               => '4155141122223339',
    'expiry_month'        => '12',
    'expiry_year'         => '26',
    'cvv'                 => '555',
    'currency_code'       => 'TRY',
    'installments_number' => 1,
    'invoice_id'          => 'INV-' . time(),
    'invoice_description' => 'Premium subscription',
    'total'               => 99.00,
    'name'                => 'John',
    'surname'             => 'Doe',
]));

// Step 2: Check result
if ($result->success) {
    $orderNo = $result->data->order_no;
    // Save $orderNo to your database for future reference
} else {
    $error = $result->error;
    // Display error to user or log
}

Workflow 3: 3D Secure Payment

Best for: most production card payments β€” adds an extra bank verification step.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Your    │────▢│ Halkode  │────▢│ Bank 3D  │────▢│ Halkode  β”‚
β”‚  Server  β”‚     β”‚ API      β”‚     β”‚ Verify   β”‚     β”‚ Callback β”‚
β”‚ paySmart β”‚     β”‚ returns  β”‚     β”‚ Page     β”‚     β”‚ return   β”‚
β”‚   3D()   β”‚     β”‚ redirect β”‚     β”‚ (OTP)    β”‚     β”‚ _url     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                         β”‚
                                                   β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”
                                                   β”‚ Your App  β”‚
                                                   β”‚ handles   β”‚
                                                   β”‚ callback  β”‚
                                                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
// Step 1: Initiate 3D payment
$result = $services->paySmart3D(new PaySmart3DRequest([
    'cc_holder_name'      => 'John Doe',
    'cc_no'               => '4155141122223339',
    'expiry_month'        => '12',
    'expiry_year'         => '26',
    'cvv'                 => '555',
    'currency_code'       => 'TRY',
    'installments_number' => 1,
    'invoice_id'          => 'INV-3D-' . time(),
    'total'               => 500.00,
    'name'                => 'John',
    'surname'             => 'Doe',
    'return_url'          => 'https://example.com/payment/3d-callback',
    'cancel_url'          => 'https://example.com/payment/cancel',
]));

// Step 2: Redirect user to 3D verification
if ($result->success) {
    return redirect($result->data->redirect_url);
}

// Step 3: Handle 3D callback (on your return_url)
// The bank redirects back after OTP verification
$invoiceId = $_POST['invoice_id'] ?? $_GET['invoice_id'];

// Step 4: Verify the payment status
$status = $services->checkstatus(new CheckstatusRequest([
    'invoice_id'   => $invoiceId,
    'merchant_key' => $client->getMerchantUID(),
]));

if ($status->success && $status->data->payment_status == 1) {
    // Payment successful β€” fulfill the order
} else {
    // Payment failed or pending
}

Workflow 4: Pre-Authorization + Capture

Best for: reserving funds first, then capturing later (e.g., hotel bookings, car rentals).

// Step 1: Pre-authorize (hold funds on the card)
$result = $services->paySmart2D(new PaySmart2DRequest([
    'cc_holder_name'      => 'John Doe',
    'cc_no'               => '4155141122223339',
    'expiry_month'        => '12',
    'expiry_year'         => '26',
    'cvv'                 => '555',
    'currency_code'       => 'TRY',
    'installments_number' => 1,
    'invoice_id'          => 'PREAUTH-' . time(),
    'total'               => 1000.00,
    'name'                => 'John',
    'surname'             => 'Doe',
    'transaction_type'    => TransactionType::PreAuthorization,
]));

// Step 2: Later β€” confirm (capture) the payment
if ($result->success) {
    $confirmed = $services->confirmPayment(new ConfirmPaymentRequest([
        'invoice_id'   => 'PREAUTH-XXX',
        'total'        => '1000.00',
        'status'       => '1',
        'merchant_key' => $client->getMerchantUID(),
        'hash_key'     => $client->generateHash('PREAUTH-XXX'),
    ]));

    if ($confirmed->success) {
        // Funds captured β€” order finalized
    }
}

Workflow 5: Refund (Full or Partial)

// Full refund
$result = $services->refundPayment(new RefundAPIRequest([
    'invoice_id'   => 'INV-ORIGINAL',
    'amount'       => 99.00,      // Full amount
    'app_id'       => $client->apiclientpublic,
    'app_secret'   => $client->apikeypublic,
    'merchant_key' => $client->getMerchantUID(),
    'hash_key'     => $client->generateHash('INV-ORIGINAL'),
]));

// Partial refund (e.g., 30 TRY of a 99 TRY charge)
$partialRefund = $services->refundPayment(new RefundAPIRequest([
    'invoice_id'   => 'INV-ORIGINAL',
    'amount'       => 30.00,      // Partial amount
    'app_id'       => $client->apiclientpublic,
    'app_secret'   => $client->apikeypublic,
    'merchant_key' => $client->getMerchantUID(),
    'hash_key'     => $client->generateHash('INV-ORIGINAL'),
]));

Workflow 6: Transaction Monitoring & Reconciliation

// Check a specific transaction
$tx = $services->getTransaction(new GetTransactionRequest([
    'invoice_id'   => 'INV-001',
    'merchant_key' => $client->getMerchantUID(),
    'hash_key'     => $client->generateHash('INV-001'),
]));

// List all transactions (for daily reconciliation)
$allTx = $services->allTransaction(new AllTransactionRequest([
    'merchant_key' => $client->getMerchantUID(),
]));

// Check payment status (polling for async flows)
$status = $services->checkstatus(new CheckstatusRequest([
    'invoice_id'   => 'INV-001',
    'merchant_key' => $client->getMerchantUID(),
]));

Workflow 7: Installment Query Before Payment

Best for: showing available installment options to the user before payment.

// Step 1: Query installments for the card BIN
$installments = $services->getInstallment(new GetInstallmentRequest([
    'bin_number'    => substr($cardNumber, 0, 6), // First 6 digits
    'total'         => 1200.00,
    'currency_code' => 'TRY',
]));

// Step 2: Display installment options to user
if ($installments->success) {
    foreach ($installments->data as $option) {
        echo "Installments: {$option->installment_number} "
           . "β€” Monthly: {$option->monthly_payment} TRY "
           . "β€” Total: {$option->total_amount} TRY\n";
    }
}

// Step 3: User selects installment count, then call paySmart2D/3D
$result = $services->paySmart3D(new PaySmart3DRequest([
    'installments_number' => 6,  // User's selection
    // ... other fields
]));

Workflow 8: POS Selection

Query available POS terminals for a card:

$pos = $services->getPos(new GetPosRequest([
    'credit_card'   => '415514',  // First 6 digits (BIN)
    'amount'        => 500.00,
    'currency_code' => 'TRY',
    'merchant_key'  => $client->getMerchantUID(),
]));

if ($pos->success) {
    // pos->data contains available POS terminals with:
    // - pos_id, card_type, card_scheme
    // - payable_amount, hash_key
    // - commission_rate, installment details
}

Token Storage

The SDK automatically manages API authentication tokens. By default, tokens are stored in-memory and discarded after each request.

Default: In-Memory Storage

// No configuration needed β€” MemoryTokenStorage is used automatically
$client = new Client([...]);

Custom: Redis Storage (Plain)

For persistent token caching across requests:

use mskayali\halkode\TokenStorageInterface;

class RedisTokenStorage implements TokenStorageInterface
{
    private \Redis $redis;

    public function __construct(\Redis $redis)
    {
        $this->redis = $redis;
    }

    public function get(string $key): ?string
    {
        $value = $this->redis->get($key);
        return $value === false ? null : $value;
    }

    public function set(string $key, string $value, int $ttl): bool
    {
        return $this->redis->setex($key, $ttl, $value);
    }
}

// Usage:
$client = new Client([
    // ... credentials
    'tokenStorage' => new RedisTokenStorage($redis),
]);

Recommended: Encrypted Redis Storage

⚠️ Security Note: API tokens stored in Redis are sensitive credentials. If your Redis instance is shared, accessible over a network, or not configured with TLS, you should encrypt tokens before storing them. This prevents token leakage if Redis is compromised.

use mskayali\halkode\TokenStorageInterface;

class EncryptedRedisTokenStorage implements TokenStorageInterface
{
    private \Redis $redis;
    private string $encryptionKey;
    private string $cipher = 'aes-256-cbc';

    /**
     * @param \Redis $redis       Connected Redis instance
     * @param string $encryptionKey A 32-byte (256-bit) secret key.
     *                              Generate once with: bin2hex(random_bytes(16))
     *                              Store securely (e.g., environment variable).
     */
    public function __construct(\Redis $redis, string $encryptionKey)
    {
        $this->redis = $redis;
        $this->encryptionKey = $encryptionKey;
    }

    public function get(string $key): ?string
    {
        $encrypted = $this->redis->get($key);
        if ($encrypted === false) {
            return null;
        }

        $data = base64_decode($encrypted);
        $ivLength = openssl_cipher_iv_length($this->cipher);
        $iv = substr($data, 0, $ivLength);
        $ciphertext = substr($data, $ivLength);

        $decrypted = openssl_decrypt($ciphertext, $this->cipher, $this->encryptionKey, OPENSSL_RAW_DATA, $iv);
        return $decrypted === false ? null : $decrypted;
    }

    public function set(string $key, string $value, int $ttl): bool
    {
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cipher));
        $ciphertext = openssl_encrypt($value, $this->cipher, $this->encryptionKey, OPENSSL_RAW_DATA, $iv);

        $encrypted = base64_encode($iv . $ciphertext);

        if ($ttl > 0) {
            return $this->redis->setex($key, $ttl, $encrypted);
        }
        return $this->redis->set($key, $encrypted);
    }
}

Setup:

// 1. Generate an encryption key ONCE and store it securely:
//    php -r "echo bin2hex(random_bytes(16));"
//    β†’ e.g., "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"

// 2. Store the key in an environment variable:
//    HALKODE_TOKEN_KEY=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

// 3. Use in your application:
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);

$client = new Client([
    'baseUrl'          => Client::BASE_URL_PROD,
    'merchantUID'      => 'your-merchant-uid',
    'apikeypublic'     => 'your-api-secret-key',
    'apiclientpublic'  => 'your-api-client-key',
    'tokenStorage'     => new EncryptedRedisTokenStorage(
        $redis,
        getenv('HALKODE_TOKEN_KEY')
    ),
]);

When to use which:

Storage Use Case
MemoryTokenStorage (default) Development, single-request scripts, stateless apps
RedisTokenStorage (plain) Production with secured Redis (TLS, private network, AUTH)
EncryptedRedisTokenStorage Production with shared/untrusted Redis, compliance requirements

TokenStorageInterface

Any class implementing TokenStorageInterface can be passed to the client:

interface TokenStorageInterface
{
    public function get(string $key): ?string;
    public function set(string $key, string $value, int $ttl): bool;
}

Error Handling

Every service call returns a response object. No exceptions are thrown for API errors β€” errors are returned in the response:

$result = $services->paySmart2D($request);

if (!$result->success) {
    // Option 1: Localized message
    echo $result->message; // e.g., "Authentication error" (en) or "Kimlik doğrulama hatası" (tr)

    // Option 2: Detailed error
    if (is_array($result->error)) {
        foreach ($result->error as $field => $messages) {
            echo "$field: " . implode(', ', $messages) . "\n";
        }
    } else {
        echo $result->error;
    }
}

Status Codes

Code English Turkish
200 OK Tamam
201 Created Oluşturuldu
400 Bad Request HatalΔ± istek
401 Authentication Error Kimlik doğrulama hatası
403 Forbidden Yasak
404 Resource Not Found Kaynak bulunamadΔ±
500 Internal Server Error Dahili Sunucu HatasΔ±

Testing

Run the test suite:

composer install
./vendor/bin/phpunit --testdox

The test suite includes 108 tests with 218 assertions covering:

Suite Tests Coverage
AutoloadTest 36 PSR-4 autoloading for all classes
MemoryTokenStorageTest 7 Interface, CRUD, TTL, overwrites
ClientTest 17 Constants, encryption, tokens, auth headers
BaseResponseTest 8 Language support, status codes
ModelTest 26 Validation rules, serialization
PaymentServicesTest 14 All 12 endpoint methods

Project Structure

mskayali/halkode/
β”œβ”€β”€ composer.json              # Package definition & PSR-4 autoload
β”œβ”€β”€ phpunit.xml                # PHPUnit configuration
β”œβ”€β”€ README.md
β”œβ”€β”€ examples/
β”‚   β”œβ”€β”€ all_services.php       # Working examples for all 12 endpoints
β”‚   β”œβ”€β”€ redis_token_storage.php # Custom Redis token storage
β”‚   └── yii2_integration.php   # Yii2 component & controller patterns
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Client.php             # HTTP client, encryption, token management
β”‚   β”œβ”€β”€ TokenStorageInterface.php
β”‚   β”œβ”€β”€ MemoryTokenStorage.php
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ BaseResponse.php
β”‚   β”‚   β”œβ”€β”€ StatusCodes.php
β”‚   β”‚   β”œβ”€β”€ TransactionType.php
β”‚   β”‚   β”œβ”€β”€ Invoice.php
β”‚   β”‚   β”œβ”€β”€ InvoiceItem.php
β”‚   β”‚   β”œβ”€β”€ GetTokenRequest.php / GetTokenBody.php
β”‚   β”‚   β”œβ”€β”€ PurchaseLinkRequest.php / PurchaseLinkBody.php
β”‚   β”‚   β”œβ”€β”€ PaySmart2DRequest.php / PaySmart2DBody.php / PaySmart2DData.php
β”‚   β”‚   β”œβ”€β”€ PaySmart3DRequest.php / PaySmart3DBody.php
β”‚   β”‚   β”œβ”€β”€ RefundAPIRequest.php / RefundAPIBody.php
β”‚   β”‚   β”œβ”€β”€ CheckstatusRequest.php / CheckstatusBody.php
β”‚   β”‚   β”œβ”€β”€ ConfirmPaymentRequest.php / ConfirmPaymentBody.php
β”‚   β”‚   β”œβ”€β”€ GetInstallmentRequest.php / GetInstallmentBody.php
β”‚   β”‚   β”œβ”€β”€ MerchantCommissionsRequest.php / MerchantCommissionsBody.php
β”‚   β”‚   β”œβ”€β”€ GetPosRequest.php / GetPosBody.php / GetPosData.php
β”‚   β”‚   β”œβ”€β”€ GetTransactionRequest.php / GetTransactionBody.php
β”‚   β”‚   β”œβ”€β”€ AllTransactionRequest.php / AllTransactionBody.php
β”‚   β”‚   └── PaymentCompleteRequest.php / PaymentCompleteBody.php
β”‚   └── services/
β”‚       └── PaymentServices.php # All 12 API endpoint methods
└── tests/
    β”œβ”€β”€ bootstrap.php
    β”œβ”€β”€ AutoloadTest.php
    β”œβ”€β”€ MemoryTokenStorageTest.php
    β”œβ”€β”€ ClientTest.php
    β”œβ”€β”€ BaseResponseTest.php
    β”œβ”€β”€ ModelTest.php
    └── PaymentServicesTest.php

License

MIT