PHPackages                             mskayali/halkode - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. mskayali/halkode

ActiveYii2-extension[Payment Processing](/categories/payments)

mskayali/halkode
================

Halkode Payment Gateway SDK for Yii2

v1.0.0(2mo ago)00MITPHPPHP &gt;=8.1

Since Mar 4Pushed 2mo agoCompare

[ Source](https://github.com/mskayali/halkode)[ Packagist](https://packagist.org/packages/mskayali/halkode)[ Docs](https://github.com/HalkOdeme/Entegrasyon)[ RSS](/packages/mskayali-halkode/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (3)Versions (2)Used By (0)

Halkode Payment Gateway SDK
===========================

[](#halkode-payment-gateway-sdk)

A PHP SDK for integrating the [Halkode](https://halkode.com.tr) 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](https://github.com/HalkOdeme/Entegrasyon). Refer to it for detailed API specifications, parameter descriptions, and response formats.

Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Payment Workflows](#payment-workflows)
- [Token Storage](#token-storage)
- [Error Handling](#error-handling)
- [Testing](#testing)
- [Project Structure](#project-structure)
- [License](#license)

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- [yiisoft/yii2](https://www.yiiframework.com/) ~2.0.0
- [yiisoft/yii2-httpclient](https://github.com/yiisoft/yii2-httpclient) ~2.0.0

Installation
------------

[](#installation)

```
composer require mskayali/halkode
```

Configuration
-------------

[](#configuration)

### As a Yii2 Component

[](#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

[](#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-urls)

EnvironmentConstantURLDevelopment`Client::BASE_URL_DEV``https://testapp.halkode.com.tr/ccpayment`Production`Client::BASE_URL_PROD``https://app.halkode.com.tr/ccpayment`Quick Start
-----------

[](#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
-------------

[](#api-reference)

### Available Services

[](#available-services)

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

MethodEndpointDescription`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

[](#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
-----------------

[](#payment-workflows)

### Workflow 1: Hosted Payment Page (Purchase Link)

[](#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)

[](#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

[](#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

[](#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)

[](#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 &amp; Reconciliation

[](#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

[](#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

[](#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
-------------

[](#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

[](#default-in-memory-storage)

```
// No configuration needed — MemoryTokenStorage is used automatically
$client = new Client([...]);
```

### Custom: Redis Storage (Plain)

[](#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

[](#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:**

StorageUse 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

[](#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
--------------

[](#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

[](#status-codes)

CodeEnglishTurkish200OKTamam201CreatedOluşturuldu400Bad RequestHatalı istek401Authentication ErrorKimlik doğrulama hatası403ForbiddenYasak404Resource Not FoundKaynak bulunamadı500Internal Server ErrorDahili Sunucu Hatası---

Testing
-------

[](#testing)

Run the test suite:

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

The test suite includes **108 tests with 218 assertions** covering:

SuiteTestsCoverageAutoloadTest36PSR-4 autoloading for all classesMemoryTokenStorageTest7Interface, CRUD, TTL, overwritesClientTest17Constants, encryption, tokens, auth headersBaseResponseTest8Language support, status codesModelTest26Validation rules, serializationPaymentServicesTest14All 12 endpoint methods---

Project Structure
-----------------

[](#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
-------

[](#license)

MIT

###  Health Score

35

—

LowBetter than 79% of packages

Maintenance86

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

71d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/00d27c7f2736db2bfc1de08541fe26a1d23289898a3f8f98fa47096872379202?d=identicon)[mskayali](/maintainers/mskayali)

---

Top Contributors

[![mskayali](https://avatars.githubusercontent.com/u/9249493?v=4)](https://github.com/mskayali "mskayali (1 commits)")

---

Tags

sdkyii2paymentgatewayhalkodeccpayment

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/mskayali-halkode/health.svg)

```
[![Health](https://phpackages.com/badges/mskayali-halkode/health.svg)](https://phpackages.com/packages/mskayali-halkode)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
