tapsilat / tapsilat-php
Client SDK for Tapsilat API
Requires
- php: >=7.4
- vlucas/phpdotenv: ^5.5
Requires (Dev)
- phpunit/phpunit: ^9.0
- squizlabs/php_codesniffer: ^3.7 || ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v2026.10.26
- v2026.10.20.3
- v2026.10.20.2
- v2026.10.20.1
- v2026.7.31.2
- v2026.7.31.1
- v2026.7.11.1
- 2026.7.10.1
- 2026.6.2.1
- 2026.4.24.1
- v2025.12.19.2
- 2025.12.19.1
- v2025.12.18.4
- v2025.12.18.3
- 2025.12.18.2
- v2025.12.5.1
- v2025.12.01.1
- v2025.11.24.1
- v2025.11.21.1
- v2025.11.14.1
- v2025.08.14.3
- v2025.08.14.2
- 2025.08.14.1
- v2025.07.14.7
- v2025.07.14.6
- v2025.07.14.5
- v2025.07.14.4
- v2025.07.14.3
- v2025.07.14.2
- v2025.07.14.1
- dev-feat/elements-sessions
- dev-fix/webhook-signature
This package is auto-updated.
Last update: 2026-10-26 11:59:28 UTC
README
Create orders and retrieve secure checkout URLs.
Requires PHP 7.4+
Installation
You can install the package via Composer:
composer require tapsilat/tapsilat-php
or
git clone https://github.com/tapsilat/tapsilat-php.git
cd tapsilat-php
composer install
Usage
Environment Setup
Create a .env file:
TAPSILAT_API_KEY=your_api_key_here
TapsilatAPI Initialization
<?php require_once 'vendor/autoload.php'; use Tapsilat\TapsilatAPI; $apiKey = $_ENV['TAPSILAT_API_KEY']; $client = new TapsilatAPI($apiKey);
Validators
The SDK includes built-in validators for common data types:
GSM Number Validation
use Tapsilat\Validators; // Valid formats $validGsm = Validators::validateGsmNumber("+905551234567"); // International with + $validGsm = Validators::validateGsmNumber("00905551234567"); // International with 00 $validGsm = Validators::validateGsmNumber("05551234567"); // National format $validGsm = Validators::validateGsmNumber("5551234567"); // Local format // Automatically cleans formatting $cleanGsm = Validators::validateGsmNumber("+90 555 123-45-67"); // Returns: "+905551234567" // Throws APIException for invalid formats try { Validators::validateGsmNumber("invalid-phone"); } catch (APIException $e) { echo "Error: " . $e->error; }
Installments Validation
use Tapsilat\Validators; // Valid installment strings $installments = Validators::validateInstallments("1,2,3,6"); // Returns: [1, 2, 3, 6] $installments = Validators::validateInstallments("1, 2, 3, 6"); // Handles spaces $installments = Validators::validateInstallments(""); // Returns: [1] (default) // Throws APIException for invalid values try { Validators::validateInstallments("1,15,abc"); // 15 > 12, abc is not a number } catch (APIException $e) { echo "Error: " . $e->error; }
Order Create Process
use Tapsilat\Models\BuyerDTO; use Tapsilat\Models\OrderCreateRequest; // GSM number will be automatically validated in createOrder $buyer = new BuyerDTO( "John", "Doe", null, // birth_date null, // city null, // country "test@example.com", // email "+90 555 123-45-67" // Will be cleaned automatically ); $order = new OrderCreateRequest(100, "TRY", "tr", $buyer); $orderResponse = $client->createOrder($order);
Get Order Details
$referenceId = "mock-uuid-reference-id"; $orderDetails = $client->getOrder($referenceId);
Get Order Details by Conversation ID
$conversationId = "mock-uuid-conversation-id"; $orderDetails = $client->getOrderByConversationId($conversationId);
Get Order List
$orderList = $client->getOrders($page = 1, $perPage = 5, $buyerId = "buy_1", $status = 1);
Get Order Submerchants
$orderList = $client->getOrderSubmerchants($page = 1, $perPage = 5);
Get Checkout URL
$referenceId = "mock-uuid-reference-id"; $checkoutUrl = $client->getCheckoutUrl($referenceId);
Order Cancel Process
$referenceId = "mock-uuid-reference-id"; $client->cancelOrder($referenceId);
Order Refund Process
use Tapsilat\Models\RefundOrderRequest; $refundData = new RefundOrderRequest(100, "mock-uuid-reference-id"); $client->refundOrder($refundData);
Order Refund All Process
$referenceId = "mock-uuid-reference-id"; $client->refundAllOrder($referenceId);
Get Order Payment Details
$referenceId = "mock-uuid-reference-id"; $client->getOrderPaymentDetails($referenceId); // You can get with conversation_id too $conversationId = "mock-uuid-conversation-id"; $client->getOrderPaymentDetails($referenceId, $conversationId);
Get Order Status
$referenceId = "mock-uuid-reference-id"; $client->getOrderStatus($referenceId);
Get Order Transactions
$referenceId = "mock-uuid-reference-id"; $client->getOrderTransactions($referenceId);
Charge Order
use Tapsilat\Models\OrderChargeRequest; $chargeData = new OrderChargeRequest("mock-uuid-reference-id"); $result = $client->chargeOrder($chargeData); if (empty($result['isSuccess'])) { // The request can succeed while the charge fails; the charge response keys are camelCase echo $result['errorCode'] . ': ' . $result['message']; }
Get Order Payments
getAllOrdersPayments() is deprecated: the API has no route for it. List one order's payments instead:
use Tapsilat\Models\GetOrderPaymentsRequest; // Arguments: order ID, conversation ID, order reference ID; set one of them $payments = $client->getOrderPayments(new GetOrderPaymentsRequest(null, null, "mock-uuid-reference-id"));
Get Order Term
$termReferenceId = "mock-uuid-term-reference-id"; $client->getOrderTerm($termReferenceId);
Create Order Term
order_id is the order's ID (from order creation), not its reference ID. Term dates use YYYY-MM-DD.
use Tapsilat\Models\OrderPaymentTermCreateRequest; $orderId = "mock-order-id"; $terms = [ new OrderPaymentTermCreateRequest( $orderId, "TERM-123000456", 5000, "2025-10-10", 1, true, "PENDING" ), new OrderPaymentTermCreateRequest( $orderId, "TERM-123000457", 5000, "2025-11-10", 2, true, "PENDING" ) ]; foreach ($terms as $term) { $client->createOrderTerm($term); }
Delete Order Term
use Tapsilat\Models\OrderPaymentTermDeleteRequest; $client->deleteOrderTerm(new OrderPaymentTermDeleteRequest("mock-uuid-order-id", "TERM-123000456"));
Update Order Term
An update replaces the term, so send every field.
use Tapsilat\Models\OrderPaymentTermUpdateRequest; $term = new OrderPaymentTermUpdateRequest( "TERM-123000457", null, // amount "2025-12-10", // due_date null, // paid_date true // required ); $client->updateOrderTerm($term);
Refund Order Term
use Tapsilat\Models\OrderTermRefundRequest; // term_id is the term's ID; the order's reference ID is required $term = new OrderTermRefundRequest("term-id", 100, "mock-uuid-reference-id"); $client->refundOrderTerm($term);
Terminate Order
$client->terminateOrder("mock-uuid-reference-id");
Manual Callback for Order
$referenceId = "mock-uuid-reference-id"; $conversationId = "mock-uuid-conversation-id"; $client->manualCallback($referenceId, $conversationId);
Order Related Reference Update
$referenceId = "mock-uuid-reference-id"; $relatedReferenceId = "mock-uuid-related-reference-id"; $client->relatedUpdate($referenceId, ["related_reference_id" => $relatedReferenceId]);
Get Organization Settings
$settings = $client->getOrganizationSettings();
Organization Management
use Tapsilat\Models\CreateOrganizationCurrencyPayload; // Create Currency $currencyPayload = new CreateOrganizationCurrencyPayload("EUR"); $client->createOrganizationCurrency($currencyPayload); // Get Organization Partners $partners = $client->getOrganizationPartners(); // Get Organization Limits by ID $limits = $client->getOrganizationLimitsById("mock-org-uuid", ["currency" => "TRY"]); // Most endpoints accept currency units directly; resolve one to its UUID where a UUID is required $currencyId = $client->resolveCurrencyId("TRY");
getOrganizationLimits(), getOrganizationLimitUser() and setOrganizationLimitUser() are deprecated: the API has no such routes.
Health Monitoring
$health = $client->healthCheck(); echo "Status: " . $health['status']; // "OK"
Submerchant Management
use Tapsilat\Models\SubmerchantCreateDTO; $submerchantCreate = new SubmerchantCreateDTO( "Address", "City", "Country", "email@example.com", "5555555555", "TR123", "12345678901", "Name", "Contact", "Surname", "PERSONAL", "Tax Office", "34000" ); $response = $client->createSubmerchant($submerchantCreate); $submerchants = $client->listSubmerchants();
Document Export (PDF & Excel)
// Returns raw PDF blob $pdfData = $client->getOrderPdf("mock-uuid-reference-id"); // file_put_contents("order.pdf", $pdfData); // Returns raw Excel blob $excelData = $client->getOrderExcel("mock-uuid-reference-id"); // file_put_contents("order.xlsx", $excelData);
Card Installments
use Tapsilat\Models\CardInstallmentsRequest; use Tapsilat\Models\InstallmentTableRequest; $options = $client->getCardInstallments(new CardInstallmentsRequest([ 'bin' => '54066200', 'amount' => 1000, 'currency' => 'TRY', 'is_three_d' => true, ])); $table = $client->getInstallmentTable(new InstallmentTableRequest(['amount' => 1000, 'currency' => 'TRY']));
Submerchant Settlement and Payout Events
use Tapsilat\Models\SubmerchantPaymentAction; use Tapsilat\Models\SubmerchantPaymentItemUpdate; use Tapsilat\Models\SubmerchantPayoutEventRequest; $action = new SubmerchantPaymentAction([ 'locale' => 'tr', 'conversation_id' => 'conv-1', 'payment_transaction_id' => 'tx-1', 'acquirer' => 'acquirer', ]); $client->approveSubmerchantPayment($action); $client->disapproveSubmerchantPayment($action); $client->updateSubmerchantPaymentItem(new SubmerchantPaymentItemUpdate([ 'locale' => 'tr', 'conversation_id' => 'conv-1', 'payment_transaction_id' => 'tx-1', 'sub_merchant_key' => 'key-1', 'sub_merchant_price' => '60.25', 'acquirer' => 'acquirer', ])); $client->recordSubmerchantPayoutEvent(new SubmerchantPayoutEventRequest([ 'idempotency_key' => 'event-1', 'order_reference' => 'reference-id', 'item_id' => 'item-1', 'event_type' => 'DELIVERED', ]));
Virtual POS
A VPOS update replaces the whole configuration (fields left out are stored as empty or false), so read it first and send every field back.
use Tapsilat\Models\VposUpdateRequest; $vpos = $client->getVpos('vpos-id'); $fields = array_intersect_key($vpos, get_class_vars(VposUpdateRequest::class)); $client->updateVpos('vpos-id', new VposUpdateRequest(array_merge($fields, ['failover' => true]))); $list = $client->listVpos(1, 10); $acquirers = $client->listVposAcquirers(); $submerchants = $client->listVposSubmerchants(1, 10, 'vpos-id');
Saved Cards
use Tapsilat\Models\CardTokenizeRequest; $result = $client->tokenizeCard(new CardTokenizeRequest([ 'card_number' => '4111111111111111', 'holder_name' => 'John Doe', 'expiry_month' => '12', 'expiry_year' => '30', 'cvv' => '123', ])); $cards = $client->listSavedCards(1, 10); $client->deleteSavedCard('card-id');
Marketplace (API v2)
Marketplace calls use the /api/v2 URL derived from the base URL. Seller and payout revisions are opaque strings:
send them back exactly as received.
use Tapsilat\Models\MarketplaceApprovalPolicyInput; use Tapsilat\Models\MarketplacePayoutApproval; use Tapsilat\Models\MarketplacePayoutFilter; use Tapsilat\Models\MarketplaceProfileUpdate; $seller = $client->getMarketplaceSubmerchant('seller-id'); $client->updateMarketplaceSubmerchant('seller-id', new MarketplaceProfileUpdate([ 'name' => 'New name', 'revision' => $seller['revision'], ])); $payouts = $client->listMarketplacePayouts(new MarketplacePayoutFilter(['status' => 'WAITING_APPROVAL', 'page' => 1])); $client->approveMarketplacePayouts(new MarketplacePayoutApproval([ 'ids' => array_column($payouts['rows'], 'id'), ])); // The approval policy currency must be a UUID; a unit such as "TRY" is resolved for you $client->createMarketplaceApprovalPolicy(new MarketplaceApprovalPolicyInput([ 'name' => 'Large orders', 'priority' => 1, 'min_amount' => '1000', 'currency_id' => 'TRY', 'mode' => 'manual', ]));
Tapsilat Elements Sessions
Tapsilat Elements is the native Android and iOS card sheet. Your backend creates a session with its secret API key and
passes only the session's client_secret to the app, which opens the sheet with it. Card data goes from the sheet to
Tapsilat encrypted and never reaches your backend.
use Tapsilat\Models\ElementsSession; use Tapsilat\Models\ElementsSessionCreateRequest; $session = $client->createElementsSession(new ElementsSessionCreateRequest([ 'purpose' => ElementsSessionCreateRequest::PURPOSE_PAY, 'order_reference_id' => $referenceId, 'save_card' => true, ])); // Return the session ID and client secret to your app. Do not log or store the secret. echo json_encode(['session_id' => $session->getId(), 'client_secret' => $session->getClientSecret()]); // Later, read the outcome on your backend instead of trusting the app $outcome = $client->getElementsSession($sessionId); if ($outcome->getStatus() === ElementsSession::STATUS_SUCCEEDED) { // fulfil the order; $outcome->getCard() has the brand, last four digits and expiry } // Cancel a session that is still waiting for a card $client->cancelElementsSession($sessionId);
A pay session needs order_reference_id. A tokenize session saves a card for user_id, with mode set to
MODE_SECURE (3D Secure), MODE_HALF_SECURE or MODE_NON_SECURE. A session's status is one of the
ElementsSession::STATUS_ constants (requires_payment_method, processing, requires_action, succeeded, failed,
expired, canceled), and getLastError() says why the last attempt failed. cancelElementsSession() returns a
session that is confirming or has completed unchanged.
Only createElementsSession() returns the client secret. var_dump() and print_r() show it as [redacted], while
toArray() and json_encode() keep it so that you can send it to the app. Errors carry a stable code, such as
elements_not_enabled (403), session_not_found (404) or unavailable (503), and retryable.
Error Handling
API errors throw Tapsilat\APIException with the HTTP status (statusCode), the body's code, the message (error,
from the body's message or error), per-field errors, the body's status, the raw body (rawBody) and
retryable, true when the error says the request can be retried. Elements routes nest the error as
{"error": {"code": "...", "message": "...", "retryable": true}}; code, error and retryable are read from that
object. A response that is not JSON, such as an HTML page from a wrong base URL, also throws.
use Tapsilat\APIException; try { $client->cancelOrder(new CancelOrderRequest("mock-uuid-reference-id")); } catch (APIException $e) { echo $e->statusCode . ' [' . $e->code . '] ' . $e->error; print_r($e->errors); }
System & Webhooks
Get System Definitions and Statuses
$orderStatuses = $client->getSystemOrderStatuses(); $basketItemTypes = $client->getSystemBasketItemTypes(); $errorCodes = $client->getSystemErrorCodes(); $paymentTermStatuses = $client->getSystemPaymentTermStatuses(); $productTypes = $client->getSystemProductTypes(); $shortcutTypes = $client->getSystemShortcutTypes(); $transactionPaymentTypes = $client->getSystemTransactionPaymentTypes(); $transactionPurposes = $client->getSystemTransactionPurposes(); $transactionStatuses = $client->getSystemTransactionStatuses();
Get System Config
$config = $client->getSystemConfig();
Webhook Handling
Verify Webhook Signature
Tapsilat signs every callback it sends to your callback URLs, and to an order's payment_success_url and payment_failure_url, with the webhook secret from the panel's callback settings. The Tapsilat-Signature header looks like t=1726480000,v1=5257a8…: v1 is the hex HMAC-SHA256 of the timestamp, a dot and the raw body.
verifyWebhook checks it in constant time and rejects a timestamp more than 5 minutes from the server clock, so a captured callback cannot be replayed. Pass the raw body, not re-encoded JSON.
use Tapsilat\TapsilatAPI; use Tapsilat\WebhookEvent; $payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_TAPSILAT_SIGNATURE'] ?? ''; $secret = 'your_webhook_secret'; if (!TapsilatAPI::verifyWebhook($payload, $signature, $secret)) { http_response_code(400); exit; } $event = WebhookEvent::fromPayload($payload); $event->type; // "order_lite" (the default), "order", "order_payment_detail", ... $event->getReferenceId(); // the order's reference ID, whatever the callback type $event->getConversationId(); $event->getStatus(); // e.g. "Paid", when the callback type carries a status if ($event->isPaid()) { // fulfil the order } http_response_code(200);
A different tolerance in seconds can be passed as the fourth argument (0 disables the timestamp check). A bare hex HMAC of the body, with or without a sha256= prefix, is still accepted for signatures made the way earlier SDK versions documented.
Order Statuses
Tapsilat\OrderStatus has the numeric status codes that callbacks carry, the names getOrderStatus() returns, and the groups most integrations need:
use Tapsilat\OrderStatus; $status = $client->getOrderStatus($referenceId)['status']; // e.g. "Waiting for payment" OrderStatus::isPaid($status); // Paid, Completed, Sub merchant payment approved OrderStatus::isFailed($status); // Cancelled, Fraud, Rejected, Failure, Expired, Terminated OrderStatus::isRefunded($status); // Refunded, Partially refunded OrderStatus::code($status); // 7; also accepts "7" or 7
Subscription Operations
Create Subscription
use Tapsilat\Models\SubscriptionCreateRequest; use Tapsilat\Models\SubscriptionBillingDTO; use Tapsilat\Models\SubscriptionUserDTO; $billing = new SubscriptionBillingDTO( "123 Main St", // address "Istanbul", // city "John Doe", // contact_name "TR", // country "1234567890", // vat_number "34000" // zip_code ); $user = new SubscriptionUserDTO( "user_123", // id "John", // first_name "Doe", // last_name "john@example.com", // email "5551234567", // phone "12345678901", // identity_number "123 Main St", // address "Istanbul", // city "TR", // country "34000" // zip_code ); $subscription = new SubscriptionCreateRequest( 100.0, // amount "TRY", // currency "Monthly Subscription", // title 30, // period (in days) 1, // cycle 1, // payment_date (day of month) "ext_sub_123", // external_reference_id "https://example.com/success", // success_url "https://example.com/failure", // failure_url "card_token_123", // card_id $billing, // billing $user // user ); $response = $client->createSubscription($subscription); echo "Subscription Reference ID: " . $response->getReferenceId(); echo "Order Reference ID: " . $response->getOrderReferenceId();
Get Subscription
use Tapsilat\Models\SubscriptionGetRequest; // Get by reference_id $request = new SubscriptionGetRequest("subscription_reference_id", null); $subscription = $client->getSubscription($request); // Or get by external_reference_id $request = new SubscriptionGetRequest(null, "ext_sub_123"); $subscription = $client->getSubscription($request); echo "Title: " . $subscription->getTitle(); echo "Amount: " . $subscription->getAmount(); echo "Is Active: " . ($subscription->getIsActive() ? 'Yes' : 'No'); echo "Payment Status: " . $subscription->getPaymentStatus();
List Subscriptions
// Get first page with 10 items per page $subscriptions = $client->listSubscriptions(1, 10); echo "Total: " . $subscriptions['total']; echo "Total Pages: " . $subscriptions['total_pages']; foreach ($subscriptions['rows'] as $subscription) { echo "Subscription: " . $subscription['title'] . " - " . $subscription['amount']; }
Cancel Subscription
use Tapsilat\Models\SubscriptionCancelRequest; // Cancel by reference_id $request = new SubscriptionCancelRequest("subscription_reference_id", null); $client->cancelSubscription($request); // Or cancel by external_reference_id $request = new SubscriptionCancelRequest(null, "ext_sub_123"); $client->cancelSubscription($request);
Redirect Subscription
use Tapsilat\Models\SubscriptionRedirectRequest; $request = new SubscriptionRedirectRequest("subscription_id"); $response = $client->redirectSubscription($request); echo "Redirect URL: " . $response->getUrl();
Requirements
- PHP >= 7.4
License
The MIT License (MIT). Please see License File for more information.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Testing
The SDK includes comprehensive unit and integration tests.
Environment Setup
For integration tests, create a .env file in the project root:
TAPSILAT_API_KEY=your_api_key_here
Running Tests
# Run all tests (unit + integration) composer test # or vendor/bin/phpunit # Run only unit tests (no API calls) vendor/bin/phpunit --testsuite=Unit # Run only integration tests (real API calls) vendor/bin/phpunit --testsuite=Integration # Run with coverage report vendor/bin/phpunit --coverage-html coverage/
Test Structure
tests/
├── Unit/ # Mock-based tests (no API calls)
│ ├── OrderTest.php # Order operations
│ ├── SubscriptionTest.php # Subscription operations
│ ├── OrganizationTest.php # Organization settings
│ └── ValidatorTest.php # Utility validators
└── Integration/ # Real API tests (requires API key)
├── OrderIntegrationTest.php
└── SubscriptionIntegrationTest.php
Test Coverage
- Unit Tests: 87 tests covering all 28 API methods
- Integration Tests: 6 tests for critical API flows
- Total: 93 tests with 228 assertions
CI/CD
This package uses GitHub Actions for continuous integration and deployment:
-
CI: Runs on every push to main/develop and pull requests
- Tests against PHP 7.4, 8.0, 8.1, 8.2, and 8.3
- Validates composer.json
- Runs PHPUnit tests
- Checks code style with PHP_CodeSniffer
-
Release & Publish: Automatically triggered when a new tag is pushed
- Creates a GitHub release (if it doesn't exist)
- Validates and tests the package
- Publishes to Packagist (if configured)
- Verifies package availability on Packagist
-
Packagist Webhook: Automatically notifies Packagist on every push to main
- Triggers on pushes to main branch and merged PRs
- Keeps Packagist updated with latest changes
- Works alongside GitHub webhook for immediate updates
-
Dependabot: Automatically updates dependencies
- Weekly updates for Composer packages
- Weekly updates for GitHub Actions
- Creates pull requests for review
To create a new release:
git tag v1.0.0 git push origin v1.0.0
This will automatically:
- Create a GitHub release
- Publish the package to Packagist
- Make it available via
composer require tapsilat/tapsilat-php
Docker Development
This package includes Docker support for easy development setup.
Quick Start
# Build the Docker image ./docker-dev.sh build # Start the development container ./docker-dev.sh start # Open a shell in the container ./docker-dev.sh shell # Run tests ./docker-dev.sh test
Available Commands
./docker-dev.sh build- Build the Docker image./docker-dev.sh start- Start the development container./docker-dev.sh stop- Stop the development container./docker-dev.sh restart- Restart the development container./docker-dev.sh shell- Open a shell in the development container./docker-dev.sh test- Run tests in the container./docker-dev.sh install- Install dependencies./docker-dev.sh clean- Clean up containers and images./docker-dev.sh help- Show help message
Manual Docker Commands
# Build and start with docker-compose docker-compose up -d tapsilat-dev # Run tests docker-compose run --rm tapsilat-test # Execute commands in the container docker-compose exec tapsilat-dev composer install docker-compose exec tapsilat-dev composer test docker-compose exec tapsilat-dev php example.php