touchestate/php-sdk 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

touchestate/php-sdk

Composer 安装命令:

composer require touchestate/php-sdk

包简介

Official PHP SDK for TouchEstate API - works with Laravel and standalone PHP

README 文档

README

Official PHP SDK for the TouchEstate API with TE-SigV4 authentication.

PHP Version Laravel Tests License

About

TouchEstate SDK is a PHP library that provides seamless integration with the TouchEstate CRM API. It handles all the complexity of cryptographic request signing, authentication, and API communication, allowing you to focus on building your real estate application.

Key Features

  • TE-SigV4 Authentication - Cryptographic request signing using HMAC-SHA256 for maximum security
  • Laravel Integration - First-class support with service provider, facade, and dependency injection
  • Standalone Usage - Works with any PHP project, not just Laravel
  • Type-Safe - Full PHP 8.1+ type hints and strict types for better IDE support
  • Clean API - Stripe-inspired fluent interface for intuitive usage
  • Comprehensive Error Handling - Specific exceptions for different error scenarios
  • Retry Logic Ready - Built-in support for retry patterns and circuit breakers
  • Production Ready - Thoroughly tested with 57+ unit and integration tests
  • Well Documented - Extensive inline documentation and usage examples

What is TE-SigV4?

TE-SigV4 (TouchEstate Signature Version 4) is a cryptographic authentication protocol used by TouchEstate API to ensure maximum security for all API requests.

Why TE-SigV4?

Traditional API authentication methods (API tokens, basic auth) have significant security weaknesses:

  • Tokens can be intercepted during transmission
  • No protection against tampering - attackers can modify request data
  • No replay protection - captured requests can be re-sent
  • Single point of failure - one leaked token compromises everything

TE-SigV4 solves all these problems through cryptographic request signing.

How It Works

Every API request is cryptographically signed using HMAC-SHA256:

1. Client calculates SHA-256 hash of request body
2. Client builds canonical request (method, path, headers, body hash)
3. Client signs the canonical request with secret key
4. Client sends request with signature in Authorization header
5. Server independently calculates signature using same algorithm
6. Server compares signatures - if they match, request is authentic

Your secret key never leaves your server - only the signature is transmitted.

Security Guarantees

  • Request Authenticity - Cryptographically proves the request came from you (only you have the secret key)
  • Request Integrity - Any modification to the request (headers, body, URL) invalidates the signature
  • Replay Protection - 5-minute timestamp window prevents captured requests from being re-sent
  • Credential Scoping - Signatures are scoped to date/service, limiting damage if compromised
  • Zero Credential Exposure - Secret keys are never transmitted over the network

Algorithm Details

TE-SigV4 uses industry-standard HMAC-SHA256 cryptography with key derivation:

  • Algorithm: TE-HMAC-SHA256
  • Scope: {date}/global/touchestate/te_request
  • Header: TE-HMAC-SHA256 Credential=..., SignedHeaders=..., Signature=...
  • Region: global (single worldwide region)
  • Service: touchestate (TouchEstate CRM only)

Why This Matters for Developers

Automatic signing - The SDK handles all signature calculation for you ✅ Maximum security - Bank-grade cryptographic authentication for your real estate data ✅ Simple usage - Just provide your TouchEstate keys and make requests ✅ Zero complexity - No manual cryptography or signature logic needed

// All signature calculation happens automatically
$properties = TouchEstate::properties()->list();

// Behind the scenes:
// 1. SDK calculates body hash
// 2. SDK builds canonical request
// 3. SDK signs with your secret key
// 4. SDK adds Authorization header
// 5. SDK sends signed request
// You don't see any of this complexity!

Installation

Install via Composer:

composer require touchestate/php-sdk

Requirements

  • PHP 8.1 or higher
  • Laravel 10.x or 11.x (for Laravel integration)
  • GuzzleHTTP 7.x

Quick Start

1. Get Your API Keys

  1. Log in to TouchEstate Dashboard
  2. Navigate to SettingsAccess Keys
  3. Click Create Access Key
  4. Copy both keys (secret key shown only once!)

2. Configure Environment

Add your credentials to .env:

TOUCHESTATE_PUBLIC_KEY=pk_live_your_public_key_here
TOUCHESTATE_SECRET_KEY=sk_live_your_secret_key_here
TOUCHESTATE_BASE_URL=https://api.touchestate.am

# Disable SSL verification only for local development on Windows
# Never set to false in production
TOUCHESTATE_VERIFY_SSL=true

3. Start Using

use TouchEstate\Sdk\Facades\TouchEstate;

// List properties
$properties = TouchEstate::properties()->list([
    'page' => 1,
    'pageSize' => 20,
]);

foreach ($properties['items'] as $property) {
    echo $property['title'] . ' - $' . number_format($property['price']) . "\n";
}

// Get single property
$property = TouchEstate::properties()->retrieve('property-id');
echo $property['title'];

Laravel Integration

Service Provider (Auto-registered)

The package automatically registers its service provider in Laravel 10+.

Publish Configuration

php artisan vendor:publish --tag=touchestate-config

This creates config/touchestate.php:

return [
    'public_key'      => env('TOUCHESTATE_PUBLIC_KEY'),
    'secret_key'      => env('TOUCHESTATE_SECRET_KEY'),
    'base_url'        => env('TOUCHESTATE_BASE_URL', 'https://api.touchestate.am'),
    'timeout'         => env('TOUCHESTATE_TIMEOUT', 30),
    'connect_timeout' => env('TOUCHESTATE_CONNECT_TIMEOUT', 10),
    'verify_ssl'      => env('TOUCHESTATE_VERIFY_SSL', true),
];

Usage Methods

1. Facade (Recommended)

use TouchEstate\Sdk\Facades\TouchEstate;

$properties = TouchEstate::properties()->list();
$contacts = TouchEstate::contacts()->list();

2. Dependency Injection

use TouchEstate\Sdk\TouchEstateClient;

class PropertyController extends Controller
{
    public function __construct(
        private TouchEstateClient $client
    ) {}

    public function index()
    {
        $properties = $this->client->properties()->list([
            'page' => 1,
            'pageSize' => 20,
        ]);

        return view('properties.index', compact('properties'));
    }
}

3. Service Container

$client = app('touchestate');
$properties = $client->properties()->list();

Standalone Usage (Without Laravel)

use TouchEstate\Sdk\TouchEstateClient;

$client = new TouchEstateClient([
    'public_key' => 'pk_live_xxx',
    'secret_key' => 'sk_live_xxx',
    'base_url' => 'https://api.touchestate.am',
]);

$properties = $client->properties()->list();

API Reference

Properties Service

List Properties

$properties = $client->properties()->list([
    'page' => 1,          // Page number (default: 1)
    'pageSize' => 20,     // Items per page (max: 100)
]);

Response:

[
    'items' => [
        [
            'id' => 'uuid',
            'title' => 'Luxury Apartment in Yerevan',
            'price' => 500000,
            'propertyType' => 'Apartment',
            'listingType' => 'Sale',
            'bedrooms' => 3,
            'bathrooms' => 2,
            'area' => 120.5,
            'address' => [...],
            // ... more fields
        ],
    ],
    'pageNumber' => 1,
    'pageSize' => 20,
    'totalCount' => 150,
    'totalPages' => 8,
    'hasNextPage' => true,
    'hasPreviousPage' => false,
]

Retrieve Single Property

$property = $client->properties()->retrieve('property-id');

Contacts Service

List Contacts

$contacts = $client->contacts()->list([
    'page' => 1,
    'pageSize' => 50,
]);

Response:

[
    'items' => [
        [
            'id' => 'uuid',
            'firstName' => 'John',
            'lastName' => 'Doe',
            'email' => 'john@example.com',
            'phone' => '+1234567890',
            'type' => 'Lead',
            // ... more fields
        ],
    ],
    'pageNumber' => 1,
    'pageSize' => 50,
    'totalCount' => 500,
    'totalPages' => 10,
]

Retrieve Single Contact

$contact = $client->contacts()->retrieve('contact-id');

Workspace Service

Get Workspace Information

Get information about your workspace (determined from your API key):

$workspace = $client->workspace()->retrieve();

// Or use the alias method
$workspace = $client->workspace()->info();

Response:

[
    'id' => 'uuid',
    'name' => 'My Real Estate Agency',
    'slug' => 'my-agency',
    'logoUrl' => 'https://cdn.touchestate.am/logos/...',
    'description' => 'Leading real estate agency in Yerevan',
    'timezone' => 'Asia/Yerevan',
    'defaultCommissionPercent' => 5.0,
    'isActive' => true,
    'createdAt' => '2024-01-01T00:00:00Z',
]

Use Cases:

  • Display workspace branding in external applications
  • Sync workspace settings and configuration
  • Validate workspace status and permissions
  • Show workspace details in CRM integrations

Error Handling

The SDK provides specific exception types for different error scenarios:

Exception Hierarchy

TouchEstateException (base)
├── AuthenticationException (401)
└── ApiException (API errors)
    ├── InvalidRequestException (4xx)
    ├── RateLimitException (429)
    └── ServerException (5xx)

Basic Error Handling

use TouchEstate\Sdk\Exception\AuthenticationException;
use TouchEstate\Sdk\Exception\InvalidRequestException;
use TouchEstate\Sdk\Exception\RateLimitException;
use TouchEstate\Sdk\Exception\ServerException;

try {
    $properties = $client->properties()->list();
} catch (AuthenticationException $e) {
    // Invalid credentials - check your keys
    Log::error('Authentication failed: ' . $e->getMessage());
} catch (InvalidRequestException $e) {
    // Bad request, not found, validation error
    Log::warning('Invalid request: ' . $e->getMessage());
    $details = $e->getResponseData(); // Get API error details
} catch (RateLimitException $e) {
    // Too many requests - implement backoff
    Log::warning('Rate limited, retrying later');
} catch (ServerException $e) {
    // Server error - retry with backoff
    Log::error('Server error: ' . $e->getMessage());
}

Retry Logic Example

function retryableRequest(callable $request, int $maxRetries = 3): mixed
{
    for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
        try {
            return $request();
        } catch (RateLimitException | ServerException $e) {
            if ($attempt === $maxRetries) {
                throw $e;
            }

            // Exponential backoff: 1s, 2s, 4s
            $delay = pow(2, $attempt - 1);
            sleep($delay);
        }
    }
}

// Usage
$properties = retryableRequest(function() use ($client) {
    return $client->properties()->list(['page' => 1]);
});

Getting Error Details

try {
    $property = $client->properties()->retrieve('invalid-id');
} catch (InvalidRequestException $e) {
    // HTTP status code
    echo $e->getCode(); // 404

    // Error message (auto-extracted from API response)
    echo $e->getMessage(); // "Resource not found"

    // Full response body
    echo $e->getResponseBody();

    // Decoded response data
    $data = $e->getResponseData();
    if ($data) {
        print_r($data);
    }
}

Advanced Usage

Custom HTTP Client

use TouchEstate\Sdk\HttpClient\GuzzleClient;

$httpClient = new GuzzleClient([
    'timeout' => 60,
    'connect_timeout' => 30,
    'verify' => true,
]);

$client = new TouchEstateClient([
    'public_key' => 'pk_live_xxx',
    'secret_key' => 'sk_live_xxx',
    'http_client' => $httpClient,
]);

Custom Headers

use TouchEstate\Sdk\Util\RequestOptions;

$options = new RequestOptions([
    'X-Request-ID' => 'unique-request-id',
]);

$properties = $client->properties()->list(['page' => 1], $options);

Environment-Specific Configuration

// config/touchestate.php
return [
    'public_key' => env('TOUCHESTATE_PUBLIC_KEY'),
    'secret_key' => env('TOUCHESTATE_SECRET_KEY'),
    'base_url' => env('APP_ENV') === 'production'
        ? 'https://api.touchestate.am'
        : 'http://localhost:5100',
    'timeout' => 30,
];

Logging

The SDK integrates with Laravel's logging system (PSR-3 compatible) to provide detailed insights into API requests, responses, and errors.

Automatic Logging

When using Laravel, the SDK automatically logs to your configured logger:

use TouchEstate\Sdk\Facades\TouchEstate;

// SDK automatically logs:
// [INFO] TouchEstate API request { method, path, params_count }
// [INFO] TouchEstate API response { method, path, status, duration_ms }
$properties = TouchEstate::properties()->list(['page' => 1]);

Log Levels

  • INFO: API requests and successful responses
  • WARNING: Rate limiting (429)
  • ERROR: Authentication failures (401), client errors (4xx), server errors (5xx)

Basic Error Handling with Logging

use TouchEstate\Sdk\Facades\TouchEstate;
use TouchEstate\Sdk\Exception\AuthenticationException;
use TouchEstate\Sdk\Exception\RateLimitException;
use TouchEstate\Sdk\Exception\ServerException;
use Illuminate\Support\Facades\Log;

try {
    // INFO: TouchEstate API request
    $properties = TouchEstate::properties()->list(['page' => 1]);

    // INFO: TouchEstate API response { status: 200, duration_ms: 125 }
    Log::info('Properties fetched successfully', [
        'count' => $properties['totalCount'],
    ]);

} catch (AuthenticationException $e) {
    // ERROR: TouchEstate API authentication failed (already logged by SDK)
    Log::critical('API authentication broken - check keys', [
        'user_id' => auth()->id(),
    ]);

    return response()->json(['error' => 'Service unavailable'], 503);

} catch (RateLimitException $e) {
    // WARNING: TouchEstate API rate limit exceeded (already logged by SDK)
    Log::warning('Rate limit hit - implementing backoff', [
        'user_id' => auth()->id(),
    ]);

    return response()->json(['error' => 'Too many requests'], 429);

} catch (ServerException $e) {
    // ERROR: TouchEstate API server error (already logged by SDK)
    Log::error('API server error - will retry', [
        'error' => $e->getMessage(),
    ]);

    return response()->json(['error' => 'Service temporarily unavailable'], 503);
}

Advanced: Retry Logic with Logging

use TouchEstate\Sdk\Exception\RateLimitException;
use TouchEstate\Sdk\Exception\ServerException;
use Illuminate\Support\Facades\Log;

function fetchPropertiesWithRetry($client, $params, $maxRetries = 3): array
{
    $attempt = 0;

    while ($attempt < $maxRetries) {
        $attempt++;

        try {
            Log::info('Fetching properties', [
                'attempt' => $attempt,
                'max_attempts' => $maxRetries,
            ]);

            $properties = $client->properties()->list($params);

            // SUCCESS
            Log::info('Properties fetched successfully', [
                'attempt' => $attempt,
                'count' => $properties['totalCount'],
            ]);

            return $properties;

        } catch (RateLimitException | ServerException $e) {
            // WARNING/ERROR already logged by SDK

            if ($attempt === $maxRetries) {
                Log::error('All retry attempts exhausted', [
                    'attempts' => $maxRetries,
                    'last_error' => $e->getMessage(),
                ]);

                throw $e;
            }

            // Exponential backoff: 1s, 2s, 4s
            $delay = pow(2, $attempt - 1);

            Log::warning('Retrying after delay', [
                'attempt' => $attempt,
                'delay_seconds' => $delay,
                'error' => $e->getMessage(),
            ]);

            sleep($delay);
        }
    }
}

// Usage
try {
    $properties = fetchPropertiesWithRetry($client, ['page' => 1]);
} catch (Exception $e) {
    return response()->json(['error' => 'Failed to fetch properties'], 500);
}

Custom Log Channel

Create a dedicated channel for TouchEstate logs:

// config/logging.php
'channels' => [
    'touchestate' => [
        'driver' => 'daily',
        'path' => storage_path('logs/touchestate.log'),
        'level' => env('APP_ENV') === 'production' ? 'warning' : 'debug',
        'days' => 14,
    ],
],

Use the custom channel:

use Illuminate\Support\Facades\Log;

$client = new TouchEstateClient([
    'public_key' => config('touchestate.public_key'),
    'secret_key' => config('touchestate.secret_key'),
    'logger' => Log::channel('touchestate'),
]);

Example Log Output

[2026-02-15 15:30:00] production.INFO: TouchEstate API request {"method":"GET","path":"/api/external/properties","params_count":2}
[2026-02-15 15:30:00] production.INFO: TouchEstate API response {"method":"GET","path":"/api/external/properties","status":200,"duration_ms":125.43}
[2026-02-15 15:30:00] production.INFO: Properties fetched successfully {"count":62}

Testing

The SDK includes comprehensive tests:

# Run all tests
vendor/bin/phpunit

# Run with coverage
vendor/bin/phpunit --coverage-html coverage

# Run specific test suite
vendor/bin/phpunit tests/Unit
vendor/bin/phpunit tests/Integration

# Pretty output
vendor/bin/phpunit --testdox

Test Coverage: 57 tests, 89 assertions, 100% coverage of core functionality.

Security Best Practices

1. Never Commit Secrets

CRITICAL: Never commit actual API keys to version control.

# ❌ NEVER commit files with actual secrets
git add .env
git add .env.production
git add .env.local

# ✅ ALWAYS keep .env files in .gitignore
# .env files should never be tracked by Git

Proper Setup:

# .env (NEVER committed - contains actual secrets)
TOUCHESTATE_PUBLIC_KEY=pk_live_abc123...
TOUCHESTATE_SECRET_KEY=sk_live_xyz789...
TOUCHESTATE_BASE_URL=https://api.touchestate.am
// config/touchestate.php (committed - contains env() references only)
return [
    'public_key' => env('TOUCHESTATE_PUBLIC_KEY'),  // ✅ Safe to commit
    'secret_key' => env('TOUCHESTATE_SECRET_KEY'),  // ✅ Safe to commit
    'base_url' => env('TOUCHESTATE_BASE_URL', 'https://api.touchestate.am'),
];

2. Use Environment Variables

Never hardcode credentials in your code:

// ❌ NEVER hardcode keys
$client = new TouchEstateClient([
    'public_key' => 'pk_live_abc123',
    'secret_key' => 'sk_live_xyz789',
]);

// ✅ ALWAYS use environment variables
$client = new TouchEstateClient([
    'public_key' => env('TOUCHESTATE_PUBLIC_KEY'),
    'secret_key' => env('TOUCHESTATE_SECRET_KEY'),
]);

// ✅ Or use Laravel config (recommended)
$client = new TouchEstateClient([
    'public_key' => config('touchestate.public_key'),
    'secret_key' => config('touchestate.secret_key'),
]);

3. Use Different Keys Per Environment

Each environment should have its own keys:

# .env.production (production server only)
TOUCHESTATE_PUBLIC_KEY=pk_live_production_key
TOUCHESTATE_SECRET_KEY=sk_live_production_key

# .env.staging (staging server only)
TOUCHESTATE_PUBLIC_KEY=pk_test_staging_key
TOUCHESTATE_SECRET_KEY=sk_test_staging_key

# .env (local development only)
TOUCHESTATE_PUBLIC_KEY=pk_test_dev_key
TOUCHESTATE_SECRET_KEY=sk_test_dev_key

4. Rotate Keys Regularly

Generate new keys periodically and revoke old ones:

  1. Create new keys in TouchEstate Dashboard
  2. Update environment variables on all servers
  3. Test the new keys work correctly
  4. Revoke old keys in the dashboard

5. Monitor Key Usage

Regularly check key usage in the TouchEstate dashboard:

  • Last used timestamp
  • Request volume
  • Any suspicious activity

6. Revoke Compromised Keys Immediately

If a key is leaked or compromised:

  1. Revoke immediately in the dashboard
  2. Generate new replacement keys
  3. Update environment variables on all servers
  4. Review recent activity for unauthorized access
  5. Investigate how the leak occurred

Troubleshooting

Authentication Fails (401)

Possible causes:

  • Invalid public/secret key
  • Key has expired
  • Signature mismatch (clock skew)

Solutions:

  • Verify keys are correct in .env
  • Check key status in dashboard
  • Ensure server time is synchronized (ntpdate or NTP service)

Rate Limit Exceeded (429)

Solutions:

  • Implement exponential backoff
  • Cache frequently accessed data
  • Reduce request frequency
  • Consider upgrading plan if needed

Connection Timeout

Solutions:

  • Increase timeout in config
  • Check network connectivity
  • Verify API endpoint URL

Invalid Response Format

Solutions:

  • Update SDK to latest version
  • Check API compatibility
  • Verify base URL is correct

Support

Changelog

1.0.0 (2026-02-15)

  • Initial release
  • TE-SigV4 authentication
  • Properties API support
  • Contacts API support
  • Laravel integration
  • Comprehensive error handling
  • 100% test coverage

License

The MIT License (MIT). Please see License File for more information.

Credits

Developed by TouchEstate Team.

Ready to build amazing real estate applications? Get your API keys now!

touchestate/php-sdk 适用场景与选型建议

touchestate/php-sdk 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 02 月 16 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「Authentication」 「Guzzle」 「calendar」 「crm」 「signature」 「laravel」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 touchestate/php-sdk 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 touchestate/php-sdk 我们能提供哪些服务?
定制开发 / 二次开发

基于 touchestate/php-sdk 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 2
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 31
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-02-16