272 lines
13 KiB
PHP
272 lines
13 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Пример: SBP QR → Номинальный счёт → Идентификация → Бенефициар
|
||
*
|
||
* Сценарий:
|
||
* 1. Генерируем SBP QR код для пополнения номинального счёта
|
||
* 2. Клиент оплачивает через QR (переводит деньги на номинальный счёт)
|
||
* 3. Получаем список неидентифицированных пополнений
|
||
* 4. Идентифицируем платёж (привязываем к конкретному бенефициару)
|
||
* 5. Проверяем статус идентификации
|
||
*/
|
||
|
||
require_once __DIR__ . '/../vendor/autoload.php';
|
||
|
||
use Apoloz\AlfaBank\AlfaBank;
|
||
use Apoloz\AlfaBank\Core\AlfaConfig;
|
||
use Apoloz\AlfaBank\Data\NominalAccounts\Identification\AmountDistributionData;
|
||
use Apoloz\AlfaBank\Data\NominalAccounts\Identification\IdentifyRequestData;
|
||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||
use Apoloz\AlfaBank\Enums\Environment;
|
||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\GetIdentifyStatusRequest;
|
||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\GetUnidentifiedTransactionsRequest;
|
||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\IdentifyTransactionRequest;
|
||
use Apoloz\AlfaBank\Sbp\Data\QrCodeRequestData;
|
||
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
||
use Apoloz\AlfaBank\Sbp\SbpConnector;
|
||
use Spatie\LaravelData\DataCollection;
|
||
|
||
echo "=== SBP QR → Номинальный счёт → Идентификация ===\n\n";
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ
|
||
// =============================================================================
|
||
|
||
// SBP QR конфигурация (mTLS + подпись)
|
||
$sbpMtlsCert = __DIR__ . '/certs/sbp_mtls.crt';
|
||
$sbpMtlsKey = __DIR__ . '/certs/sbp_mtls.key';
|
||
$sbpSigningKey = __DIR__ . '/certs/sbp_signing.key'; // 4096-bit для подписи
|
||
$sbpKeyAlias = 'your-alias-from-bank';
|
||
$sbpTermNo = '30003970'; // Номер терминала
|
||
$sbpCaBundle = __DIR__ . '/certs/russian_trusted_ca-bundle.crt';
|
||
|
||
// BaaS конфигурация (для номинальных счетов)
|
||
$baasCert = __DIR__ . '/certs/baas_transport.crt';
|
||
$baasKey = __DIR__ . '/certs/baas_transport.key';
|
||
$baasClientId = getenv('ALFA_BAAS_CLIENT_ID');
|
||
$baasClientSecret = getenv('ALFA_BAAS_CLIENT_SECRET');
|
||
$baasCaBundle = __DIR__ . '/certs/russian_trusted_ca-bundle.crt';
|
||
|
||
// Данные для идентификации
|
||
$nominalAccountNumber = '40702810XXXXXXXXXXX'; // Ваш номинальный счёт
|
||
$beneficiaryId = '3fa85f64-5717-4562-b3fc-2c963f66afa6'; // ID бенефициара
|
||
|
||
echo "Конфигурация:\n";
|
||
echo " Номинальный счёт: $nominalAccountNumber\n";
|
||
echo " Бенефициар ID: $beneficiaryId\n\n";
|
||
|
||
// =============================================================================
|
||
// ЭТАП 1: Генерация SBP QR кода для пополнения номинального счёта
|
||
// =============================================================================
|
||
|
||
echo "=== ЭТАП 1: Генерация SBP QR кода ===\n";
|
||
|
||
try {
|
||
// Создаём SBP connector с раздельными ключами (mTLS + signing)
|
||
$sbpConfig = new AlfaConfig(
|
||
baseUrl: Environment::SBP_SANDBOX->value,
|
||
certificatePath: $sbpMtlsCert,
|
||
privateKeyPath: $sbpMtlsKey,
|
||
caBundlePath: $sbpCaBundle,
|
||
signingPrivateKeyPath: $sbpSigningKey // 4096-bit ключ для подписи
|
||
);
|
||
|
||
$sbp = new SbpConnector($sbpConfig, $sbpKeyAlias);
|
||
|
||
// Создаём динамический QR код с уникальным OrderNumber для идентификации
|
||
$orderNumber = 'ORDER-' . uniqid();
|
||
|
||
$qrPayload = new QrCodeRequestData(
|
||
termNo: $sbpTermNo,
|
||
qrcType: '02', // Dynamic QR
|
||
amount: '50000', // 500.00 RUB
|
||
currency: 'RUB',
|
||
paymentPurpose: "Пополнение номинального счета {$nominalAccountNumber}",
|
||
orderNumber: $orderNumber, // Уникальный номер заказа для отслеживания
|
||
messageID: 'sbp-' . uniqid(),
|
||
qrTtl: '60', // QR активен 60 минут
|
||
notificationUrl: 'https://your-domain.com/webhooks/sbp', // Webhook для уведомлений
|
||
width: '300',
|
||
height: '300'
|
||
);
|
||
|
||
$request = new GetQrCodeRequest($qrPayload);
|
||
$response = $sbp->send($request);
|
||
$qrData = $response->dto();
|
||
|
||
if ($qrData->ErrorCode !== 0) {
|
||
throw new \RuntimeException("QR generation failed: {$qrData->message}");
|
||
}
|
||
|
||
echo "✅ QR код сгенерирован!\n";
|
||
echo " Order Number: $orderNumber\n";
|
||
echo " QRC ID: {$qrData->qrcId}\n";
|
||
echo " QR Payload: {$qrData->payload}\n";
|
||
echo " Status: {$qrData->status}\n";
|
||
echo "\n💡 Клиент должен отсканировать QR и оплатить {$qrData->payload}\n\n";
|
||
|
||
// Сохраняем QR изображение для показа клиенту
|
||
if (! empty($qrData->image)) {
|
||
$qrImagePath = __DIR__ . '/qr_payment.png';
|
||
file_put_contents($qrImagePath, base64_decode($qrData->image));
|
||
echo " 📁 QR изображение сохранено: $qrImagePath\n\n";
|
||
}
|
||
|
||
echo "⏳ Ждём, пока клиент оплатит...\n";
|
||
echo " (В тестовой среде используйте Sandbox для эмуляции оплаты)\n\n";
|
||
|
||
// =============================================================================
|
||
// ЭТАП 2: Получение неидентифицированных пополнений
|
||
// =============================================================================
|
||
|
||
echo "=== ЭТАП 2: Поиск неидентифицированных пополнений ===\n";
|
||
echo " (После оплаты через SBP деньги поступают на номинальный счёт\n";
|
||
echo " как 'неидентифицированное пополнение')\n\n";
|
||
|
||
// Инициализируем BaaS connector (требуется H2H авторизация)
|
||
$baas = AlfaBank::baas(
|
||
certificatePath: $baasCert,
|
||
privateKeyPath: $baasKey,
|
||
environment: Environment::SANDBOX,
|
||
clientId: $baasClientId,
|
||
clientSecret: $baasClientSecret
|
||
);
|
||
|
||
// Авторизация через Client Credentials Flow
|
||
$baas->login($baasClientId, $baasClientSecret, ['beneficiary-payments']);
|
||
|
||
// Получаем список неидентифицированных пополнений
|
||
$unidentifiedRequest = new GetUnidentifiedTransactionsRequest(
|
||
accountNumber: $nominalAccountNumber,
|
||
limit: 10,
|
||
offset: 0
|
||
);
|
||
|
||
$unidentifiedResponse = $baas->send($unidentifiedRequest);
|
||
$unidentifiedCollection = $unidentifiedResponse->dto();
|
||
|
||
echo 'Найдено неидентифицированных пополнений: ' . count($unidentifiedCollection->items) . "\n\n";
|
||
|
||
if (count($unidentifiedCollection->items) === 0) {
|
||
echo "⚠️ Нет неидентифицированных пополнений. Возможные причины:\n";
|
||
echo " - Платёж ещё не поступил (нужно время на обработку)\n";
|
||
echo " - Неправильный номер номинального счёта\n";
|
||
echo " - Платёж уже идентифицирован\n";
|
||
exit(0);
|
||
}
|
||
|
||
// Ищем нужную транзакцию (обычно по сумме или назначению платежа)
|
||
$targetTransaction = null;
|
||
foreach ($unidentifiedCollection->items as $transaction) {
|
||
echo " Транзакция ID: {$transaction->id}\n";
|
||
echo " Сумма: {$transaction->amount} RUB\n";
|
||
echo " Назначение: {$transaction->purpose}\n";
|
||
echo " Дата: {$transaction->date}\n";
|
||
echo " Плательщик: {$transaction->payer?->name}\n\n";
|
||
|
||
// В реальном сценарии ищем по сумме, назначению или другим критериям
|
||
// Здесь берём первую для примера
|
||
if ($targetTransaction === null) {
|
||
$targetTransaction = $transaction;
|
||
}
|
||
}
|
||
|
||
if ($targetTransaction === null) {
|
||
throw new \RuntimeException('Не найдена подходящая транзакция для идентификации');
|
||
}
|
||
|
||
echo "✅ Выбрана транзакция для идентификации: {$targetTransaction->id}\n";
|
||
echo " Сумма: {$targetTransaction->amount} RUB\n\n";
|
||
|
||
// =============================================================================
|
||
// ЭТАП 3: Идентификация платежа (привязка к бенефициару)
|
||
// =============================================================================
|
||
|
||
echo "=== ЭТАП 3: Идентификация платежа ===\n";
|
||
echo " Привязываем платёж к бенефициару: $beneficiaryId\n\n";
|
||
|
||
// Подготавливаем распределение суммы
|
||
// Можно распределить между несколькими бенефициарами
|
||
$amountDistribution = DataCollection::create([
|
||
new AmountDistributionData(
|
||
beneficiaryId: $beneficiaryId,
|
||
amount: $targetTransaction->amount
|
||
),
|
||
], AmountDistributionData::class);
|
||
|
||
// Подготавливаем данные для идентификации
|
||
$identifyData = new IdentifyRequestData(
|
||
transactionId: $targetTransaction->id,
|
||
accountNumber: $nominalAccountNumber,
|
||
amountDistribution: $amountDistribution,
|
||
// Подпись добавляется через sign() метод
|
||
digestSignatures: DataCollection::create([
|
||
new DigestSignatureData(
|
||
base64Encoded: 'base64-encoded-signature-here', // Реальная подпись
|
||
signatureType: 'rsa'
|
||
),
|
||
], DigestSignatureData::class)
|
||
);
|
||
|
||
// Создаём запрос на идентификацию
|
||
$identifyRequest = new IdentifyTransactionRequest(
|
||
id: $targetTransaction->id,
|
||
data: $identifyData
|
||
);
|
||
|
||
$identifyResponse = $baas->send($identifyRequest);
|
||
$identifyResult = $identifyResponse->dto();
|
||
|
||
echo "✅ Запрос на идентификацию создан!\n";
|
||
echo " Identify ID: {$identifyResult->identifyId}\n";
|
||
echo " Статус: {$identifyResult->status}\n\n";
|
||
|
||
// =============================================================================
|
||
// ЭТАП 4: Проверка статуса идентификации
|
||
// =============================================================================
|
||
|
||
echo "=== ЭТАП 4: Проверка статуса ===\n\n";
|
||
|
||
$statusRequest = new GetIdentifyStatusRequest($identifyResult->identifyId);
|
||
$statusResponse = $baas->send($statusRequest);
|
||
$statusResult = $statusResponse->dto();
|
||
|
||
echo "Статус идентификации: {$statusResult->status}\n";
|
||
echo "Возможные статусы:\n";
|
||
echo " - PENDING: В обработке\n";
|
||
echo " - VALIDATED: Провалидировано\n";
|
||
echo " - COMPLETE: Успешно завершено\n";
|
||
echo " - FAILED: Ошибка\n\n";
|
||
|
||
if ($statusResult->status === 'COMPLETE') {
|
||
echo "🎉 ИДЕНТИФИКАЦИЯ УСПЕШНО ЗАВЕРШЕНА!\n";
|
||
echo " Деньги {$targetTransaction->amount} RUB зачислены бенефициару $beneficiaryId\n\n";
|
||
}
|
||
|
||
// =============================================================================
|
||
// РЕЗЮМЕ
|
||
// =============================================================================
|
||
|
||
echo "=== РЕЗЮМЕ ===\n";
|
||
echo "✅ 1. Сгенерирован SBP QR код\n";
|
||
echo "✅ 2. Клиент оплатил через SBP (эмитация в тесте)\n";
|
||
echo "✅ 3. Деньги поступили на номинальный счёт\n";
|
||
echo "✅ 4. Платёж идентифицирован и привязан к бенефициару\n";
|
||
echo "\n💡 Теперь бенефициар может распоряжаться этими средствами\n";
|
||
echo " (например, запросить выплату через CreatePayoffRequest)\n";
|
||
|
||
} catch (\Apoloz\AlfaBank\Exceptions\AlfaBankException $e) {
|
||
echo "\n❌ AlfaBankException: {$e->getMessage()}\n";
|
||
if ($e->getResponse()) {
|
||
echo "Ответ API: {$e->getResponse()->body()}\n";
|
||
}
|
||
} catch (\Exception $e) {
|
||
echo "\n❌ Ошибка: {$e->getMessage()}\n";
|
||
echo "Файл: {$e->getFile()}:{$e->getLine()}\n";
|
||
}
|
||
|
||
echo "\n=== Пример завершён ===\n";
|