mskayali / halkode
Halkode Payment Gateway SDK for Yii2
Requires
- php: >=8.1
- yiisoft/yii2: ~2.0.0
- yiisoft/yii2-httpclient: ~2.0.0
Requires (Dev)
- phpunit/phpunit: ^10.0
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
- Configuration
- Quick Start
- API Reference
- Payment Workflows
- Token Storage
- Error Handling
- Testing
- Project Structure
- License
Requirements
- PHP >= 8.1
- yiisoft/yii2 ~2.0.0
- yiisoft/yii2-httpclient ~2.0.0
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