> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bafanglaicai88.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Describe only the public 123hub merchant API documented on this site. The primary endpoint is POST /public/api/multihub/v1.
> Preserve API method names, field names, header names, and error codes exactly as documented. Do not invent endpoints or parameters.
> Treat every amount as an integer in minor units unless a page explicitly states otherwise.
> Never expose, request, or fabricate a merchant secret key. The SDK pages contain reference implementations, not official SDK packages.

# PHP Examples

> Integrate with the 123hub API using PHP

# PHP Examples

Complete PHP example client for integrating with the 123hub API using cURL and `hash('sha512', ...)`.

<Info>
  This is a reference implementation, not an official SDK package. It works with
  PHP 8.0+ and requires only the built-in cURL and JSON extensions.
</Info>

## Configuration

Store your credentials in environment variables or a `.env` file:

```bash theme={null}
export PAYMENT_API_APP_ID="1"
export PAYMENT_API_VERSION="2"
export PAYMENT_API_SECRET_KEY="your_secret_key"
```

## PaymentApiClient Class

A complete, reusable client class for all API operations:

```php theme={null}
<?php

class PaymentApiClient
{
    private string $appId;
    private string $secretKey;
    private string $baseUrl;
    private int $apiVersion;

    public function __construct(
        ?string $appId = null,
        ?string $secretKey = null,
        ?int $apiVersion = null,
        string $baseUrl = 'https://api.bafanglaicai88.com'
    ) {
        $this->appId = $appId ?? getenv('PAYMENT_API_APP_ID') ?: '';
        $this->secretKey = $secretKey ?? getenv('PAYMENT_API_SECRET_KEY') ?: '';
        $this->apiVersion = $apiVersion ?? (int) (getenv('PAYMENT_API_VERSION') ?: 1);
        $this->baseUrl = $baseUrl;

        if (!$this->appId || !$this->secretKey) {
            throw new InvalidArgumentException('appId and secretKey are required');
        }
        if (!in_array($this->apiVersion, [1, 2], true)) {
            throw new InvalidArgumentException('apiVersion must be 1 or 2');
        }
    }

    /**
     * Sign and send a request to the 123hub API.
     */
    private function send(array $body): array
    {
        $bodyStr = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $path = '/public/api/multihub/v' . $this->apiVersion;
        $headers = [
            'Content-Type: application/json',
            'X-Data-Application-Id: ' . $this->appId,
        ];
        $requestNonce = null;
        if ($this->apiVersion === 1) {
            $headers[] = 'X-Data-Hash: ' . hash('sha512', $bodyStr . $this->secretKey);
        } else {
            $timestamp = (string) time();
            $nonce = bin2hex(random_bytes(24));
            $requestNonce = $nonce;
            $canonical = implode("\n", [
                'quadpay-multihub-request-v2',
                $this->appId,
                'POST',
                '/public/api/multihub/v2',
                $timestamp,
                $nonce,
                hash('sha256', $bodyStr),
            ]);
            $headers[] = 'X-Data-Timestamp: ' . $timestamp;
            $headers[] = 'X-Data-Nonce: ' . $nonce;
            $headers[] = 'X-Data-Signature: ' . hash_hmac('sha512', $canonical, $this->secretKey);
        }

        $ch = curl_init();
        $responseHeaders = [];
        curl_setopt_array($ch, [
            CURLOPT_URL => $this->baseUrl . $path,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $bodyStr,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => 3,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_HEADERFUNCTION => static function ($curl, string $line) use (&$responseHeaders): int {
                $length = strlen($line);
                if (str_contains($line, ':')) {
                    [$name, $value] = explode(':', $line, 2);
                    $responseHeaders[strtolower(trim($name))] = trim($value);
                }
                return $length;
            },
        ]);

        $response = curl_exec($ch);
        $curlError = curl_error($ch);
        $httpStatus = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);

        if ($curlError) {
            throw new RuntimeException('cURL error: ' . $curlError);
        }
        if (!is_string($response)) {
            throw new RuntimeException('Empty gateway response');
        }

        $decoded = json_decode($response, true);
        if ($decoded === null) {
            throw new RuntimeException('Invalid JSON response');
        }
        if ($this->apiVersion === 2) {
            $requestId = $responseHeaders['x-request-id'] ?? $decoded['request_id'] ?? '';
            $canonicalResponse = implode("\n", [
                'quadpay-multihub-response-v2',
                $this->appId,
                $requestNonce,
                $requestId,
                (string) $httpStatus,
                hash('sha256', $response),
            ]);
            $expected = hash_hmac('sha512', $canonicalResponse, $this->secretKey);
            $received = $responseHeaders['x-data-signature'] ?? '';
            if (!$requestId || !preg_match('/^[0-9a-f]{128}$/', $received) || !hash_equals($expected, $received)) {
                throw new RuntimeException('Merchant API v2 response signature verification failed');
            }
        }

        return $decoded;
    }

    /**
     * Health check / connectivity test.
     */
    public function ping(): array
    {
        return $this->send(['method' => 'gateway.ping', 'params' => new \stdClass()]);
    }

    /**
     * Create a deposit (pay-in) payment.
     */
    public function createDeposit(
        int $serviceId,
        int $amount,
        string $currency,
        string $cId,
        array $extra = []
    ): array {
        return $this->send([
            'method' => 'payment.in',
            'service_id' => $serviceId,
            'params' => [
                'payment' => array_merge([
                    'identifiers' => ['c_id' => $cId],
                    'amount' => ['value' => $amount, 'currency' => $currency],
                ], $extra),
            ],
        ]);
    }

    /**
     * Create a withdrawal (payout) payment.
     */
    public function createWithdrawal(
        int $serviceId,
        int $amount,
        string $currency,
        string $cId,
        array $receiver,
        array $extra = []
    ): array {
        return $this->send([
            'method' => 'payment.out',
            'service_id' => $serviceId,
            'params' => [
                'payment' => array_merge([
                    'identifiers' => ['c_id' => $cId],
                    'amount' => ['value' => $amount, 'currency' => $currency],
                    'receiver' => $receiver,
                ], $extra),
            ],
        ]);
    }

    /**
     * Check payment status by identifiers.
     */
    public function getStatus(?string $cId = null, ?string $hId = null): array
    {
        $identifiers = [];
        if ($cId !== null) $identifiers['c_id'] = $cId;
        if ($hId !== null) $identifiers['h_id'] = $hId;

        return $this->send([
            'method' => 'payment.status',
            'params' => [
                'payment' => ['identifiers' => $identifiers],
            ],
        ]);
    }

    /**
     * Retrieve account balances.
     */
    public function getBalance(): array
    {
        return $this->send(['method' => 'balance.get', 'params' => new \stdClass()]);
    }
}
```

## Usage Examples

### Ping (Health Check)

```php theme={null}
<?php
require_once 'PaymentApiClient.php';

$client = new PaymentApiClient('1', 'your_secret_key', 2);

$result = $client->ping();

if ($result['success']) {
    echo "Gateway is up\n";
} else {
    echo "Error: {$result['error']['code']} - {$result['error']['message']}\n";
}
```

### Create a Deposit Payment

The examples below use INR (India), but the same client works for all supported currencies (MXN, ARS, TRY, AUD, LKR, UYU) -- just change the `serviceId`, `currency`, and `country` values.

```php theme={null}
<?php
$client = new PaymentApiClient('1', 'your_secret_key', 2);

$result = $client->createDeposit(
    serviceId: 14701,
    amount: 10000,
    currency: 'INR',
    cId: '12345',
    extra: [
        'description' => 'Order #12345',
        'payer' => [
            'email' => 'customer@example.com',
            'person' => [
                'first_name' => 'John',
                'last_name' => 'Doe',
            ],
        ],
    ],
);

if ($result['success']) {
    echo "Payment created successfully\n";
    echo "Request ID: {$result['request_id']}\n";
} else {
    echo "Error: {$result['error']['code']} - {$result['error']['message']}\n";
}
```

### Create a Withdrawal (Payout)

```php theme={null}
<?php
$result = $client->createWithdrawal(
    serviceId: 14701,
    amount: 5000,
    currency: 'INR',
    cId: '67890',
    receiver: [
        'bank' => [
            'account' => ['id' => '1234567890'],
            'ifsc' => 'SBIN0001234',
        ],
        'person' => [
            'first_name' => 'Jane',
            'last_name' => 'Doe',
        ],
    ],
    extra: [
        'description' => 'Payout #67890',
    ],
);

if ($result['success']) {
    echo "Withdrawal created successfully\n";
} else {
    echo "Error: {$result['error']['code']} - {$result['error']['message']}\n";
}
```

### Check Payment Status

```php theme={null}
<?php
// Look up by client-side ID
$result = $client->getStatus(cId: '12345');

if ($result['success']) {
    $status = $result['result']['payment']['status']['status'];
    echo "Payment status: {$status}\n";
} else {
    echo "Error: {$result['error']['code']} - {$result['error']['message']}\n";
}

// Look up by hub-side ID
$result = $client->getStatus(hId: '999001');
```

### Get Balance

```php theme={null}
<?php
$result = $client->getBalance();

if ($result['success']) {
    foreach ($result['result']['balance']['amounts'] as $amount) {
        echo "{$amount['currency']}: {$amount['value']}\n";
    }
} else {
    echo "Error: {$result['error']['code']} - {$result['error']['message']}\n";
}
```

## Error Handling

Successful responses return HTTP 200, errors return HTTP 400. Always use the `success` field to determine the outcome:

```php theme={null}
<?php
$result = $client->createDeposit(14701, 10000, 'INR', '12345');

if ($result['success']) {
    // Operation succeeded
    echo "Request ID: {$result['request_id']}\n";
    echo "Processing time: {$result['processing_time']}ms\n";
} else {
    // Operation failed
    $error = $result['error'];
    echo "Error code: {$error['code']}\n";
    echo "Error message: {$error['message']}\n";

    switch ($error['code']) {
        case 3000:
            echo "Authentication failed - check your secret key\n";
            break;
        case 1005:
            echo "Invalid parameters - check request body\n";
            break;
        case 7002:
            echo "Service temporarily unavailable - retry later\n";
            break;
        default:
            echo "Unhandled error code: {$error['code']}\n";
    }
}
```

## Handling Network Errors

Network-level errors can still occur independently of API responses:

```php theme={null}
<?php
try {
    $result = $client->ping();
    if ($result['success']) {
        echo "API is reachable\n";
    } else {
        echo "API error: {$result['error']['message']}\n";
    }
} catch (RuntimeException $e) {
    echo "Network error: {$e->getMessage()}\n";
} catch (InvalidArgumentException $e) {
    echo "Configuration error: {$e->getMessage()}\n";
}
```

## Laravel Integration

```php theme={null}
<?php
// app/Services/PaymentApiService.php

namespace App\Services;

class PaymentApiService
{
    private string $appId;
    private int $apiVersion;
    private string $secretKey;
    private string $baseUrl;

    public function __construct()
    {
        $this->appId = config('services.payment_api.app_id');
        $this->apiVersion = (int) config('services.payment_api.api_version');
        $this->secretKey = config('services.payment_api.secret_key');
        $this->baseUrl = config('services.payment_api.base_url', 'https://api.bafanglaicai88.com');
        if (!in_array($this->apiVersion, [1, 2], true)) {
            throw new \InvalidArgumentException('api_version must be 1 or 2');
        }
    }

    private function send(array $body): array
    {
        $bodyStr = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $path = '/public/api/multihub/v' . $this->apiVersion;
        $headers = [
            'X-Data-Application-Id' => $this->appId,
        ];
        if ($this->apiVersion === 1) {
            $headers['X-Data-Hash'] = hash('sha512', $bodyStr . $this->secretKey);
        } else {
            $timestamp = (string) time();
            $nonce = bin2hex(random_bytes(24));
            $canonical = implode("\n", [
                'quadpay-multihub-request-v2',
                $this->appId,
                'POST',
                '/public/api/multihub/v2',
                $timestamp,
                $nonce,
                hash('sha256', $bodyStr),
            ]);
            $headers['X-Data-Timestamp'] = $timestamp;
            $headers['X-Data-Nonce'] = $nonce;
            $headers['X-Data-Signature'] = hash_hmac('sha512', $canonical, $this->secretKey);
        }

        $response = \Illuminate\Support\Facades\Http::connectTimeout(3)
            ->timeout(30)
            ->withHeaders($headers)
            ->withBody($bodyStr, 'application/json')
            ->post($this->baseUrl . $path);

        $responseBody = $response->body();
        $decoded = json_decode($responseBody, true, flags: JSON_THROW_ON_ERROR);
        if ($this->apiVersion === 2) {
            $requestId = $response->header('X-Request-ID') ?? ($decoded['request_id'] ?? '');
            $canonicalResponse = implode("\n", [
                'quadpay-multihub-response-v2',
                $this->appId,
                $nonce,
                $requestId,
                (string) $response->status(),
                hash('sha256', $responseBody),
            ]);
            $expected = hash_hmac('sha512', $canonicalResponse, $this->secretKey);
            $received = $response->header('X-Data-Signature') ?? '';
            if ($requestId === '' || !hash_equals($expected, $received)) {
                throw new \RuntimeException('Merchant API v2 response signature verification failed');
            }
        }
        return $decoded;
    }

    public function ping(): array
    {
        return $this->send(['method' => 'gateway.ping', 'params' => new \stdClass()]);
    }

    public function createDeposit(int $serviceId, int $amount, string $currency, string $cId, array $extra = []): array
    {
        return $this->send([
            'method' => 'payment.in',
            'service_id' => $serviceId,
            'params' => [
                'payment' => array_merge([
                    'identifiers' => ['c_id' => $cId],
                    'amount' => ['value' => $amount, 'currency' => $currency],
                ], $extra),
            ],
        ]);
    }

    public function getStatus(?string $cId = null, ?string $hId = null): array
    {
        $identifiers = [];
        if ($cId !== null) $identifiers['c_id'] = $cId;
        if ($hId !== null) $identifiers['h_id'] = $hId;

        return $this->send([
            'method' => 'payment.status',
            'params' => ['payment' => ['identifiers' => $identifiers]],
        ]);
    }

    public function getBalance(): array
    {
        return $this->send(['method' => 'balance.get', 'params' => new \stdClass()]);
    }
}
```

```php theme={null}
<?php
// config/services.php (add to existing config)

return [
    // ...
    'payment_api' => [
        'app_id' => env('PAYMENT_API_APP_ID'),
        'api_version' => env('PAYMENT_API_VERSION', 1),
        'secret_key' => env('PAYMENT_API_SECRET_KEY'),
        'base_url' => env('PAYMENT_API_BASE_URL', 'https://api.bafanglaicai88.com'),
    ],
];
```

## Complete Example

```php theme={null}
<?php
/**
 * 123hub API Integration Example
 *
 * Demonstrates the complete flow: ping, check balance, create a deposit,
 * and poll for status.
 */

require_once 'PaymentApiClient.php';

function main(): void
{
    $appId = getenv('PAYMENT_API_APP_ID') ?: '1';
    $apiVersion = (int) (getenv('PAYMENT_API_VERSION') ?: 2);
    $secretKey = getenv('PAYMENT_API_SECRET_KEY') ?: 'your_secret_key';

    $client = new PaymentApiClient($appId, $secretKey, $apiVersion);

    // 1. Ping the gateway
    $pingResult = $client->ping();
    if (!$pingResult['success']) {
        echo "Gateway unreachable: {$pingResult['error']['message']}\n";
        return;
    }
    echo "Gateway is up\n";

    // 2. Check balance
    $balanceResult = $client->getBalance();
    if ($balanceResult['success']) {
        foreach ($balanceResult['result']['balance']['amounts'] as $b) {
            echo "  {$b['currency']}: {$b['value']}\n";
        }
    } else {
        echo "Balance check failed: {$balanceResult['error']['message']}\n";
    }

    // 3. Create a deposit
    $orderId = (string) time();
    $depositResult = $client->createDeposit(
        serviceId: 14701,
        amount: 10000,
        currency: 'INR',
        cId: $orderId,
        extra: [
            'description' => "Order #{$orderId}",
            'payer' => [
                'email' => 'customer@example.com',
                'person' => ['first_name' => 'John', 'last_name' => 'Doe'],
            ],
        ],
    );

    if ($depositResult['success']) {
        echo "Deposit created (request_id={$depositResult['request_id']})\n";
    } else {
        echo "Deposit failed: {$depositResult['error']['message']}\n";
        return;
    }

    // 4. Poll for status
    for ($i = 0; $i < 5; $i++) {
        sleep(3);
        $statusResult = $client->getStatus(cId: $orderId);
        if ($statusResult['success']) {
            $status = $statusResult['result']['payment']['status']['status'];
            echo "Payment status: {$status}\n";
            if (in_array($status, ['success', 'error', 'canceled', 'declined', 'refunded', 'partially_refunded'])) {
                break;
            }
        } else {
            echo "Status check failed: {$statusResult['error']['message']}\n";
            break;
        }
    }
}

main();
```
