| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- <?php
- namespace Controllers;
- use Libs\ResponseLib;
- use Models\CommodityModel;
- use Models\CprModel;
- use Models\PaymentModel;
- use Psr\Http\Message\ServerRequestInterface;
- use Services\B3CprService;
- use Services\TokenCreateService;
- class PaymentConfirmController
- {
- private PaymentModel $paymentModel;
- private CprModel $cprModel;
- private B3CprService $b3Service;
- private CommodityModel $commodityModel;
- private TokenCreateService $tokenCreateService;
- private \PDO $pdo;
- public function __construct()
- {
- if (!isset($GLOBALS['pdo']) || !$GLOBALS['pdo'] instanceof \PDO) {
- throw new \RuntimeException('Global PDO connection not initialized');
- }
- $this->pdo = $GLOBALS['pdo'];
- $this->paymentModel = new PaymentModel();
- $this->cprModel = new CprModel();
- $this->b3Service = new B3CprService();
- $this->commodityModel = new CommodityModel();
- $this->tokenCreateService = new TokenCreateService();
- }
- public function __invoke(ServerRequestInterface $request)
- {
- $body = json_decode((string)$request->getBody(), true) ?? [];
- $paymentId = isset($body['payment_id']) ? (int)$body['payment_id'] : 0;
- if ($paymentId <= 0) {
- return ResponseLib::sendFail('payment_id inválido', [], 'E_VALIDATE')->withStatus(400);
- }
- $payment = $this->paymentModel->findById($paymentId);
- if (!$payment) {
- return ResponseLib::sendFail('Pagamento não encontrado', [], 'E_NOT_FOUND')->withStatus(404);
- }
- $statusId = (int)($payment['status_id'] ?? 0);
- if ($statusId === 0) {
- return ResponseLib::sendFail('Pagamento ainda não confirmado', ['payment_id' => $paymentId], 'E_PAYMENT_PENDING')->withStatus(409);
- }
- if ($statusId !== 1) {
- return ResponseLib::sendFail('Pagamento em status inválido', ['status_id' => $statusId], 'E_PAYMENT_STATUS')->withStatus(409);
- }
- $cpr = $this->cprModel->findByPaymentId($paymentId);
- if (!$cpr) {
- return ResponseLib::sendFail('Nenhuma CPR vinculada ao pagamento', [], 'E_CPR_NOT_FOUND')->withStatus(404);
- }
- try {
- $payload = $this->b3Service->mapToB3($cpr);
- } catch (\Throwable $e) {
- return ResponseLib::sendFail('Falha ao montar payload para a B3: ' . $e->getMessage(), [], 'E_B3_MAP')->withStatus(500);
- }
- try {
- $token = $this->resolveB3Token($request, $body);
- } catch (\Throwable $e) {
- return ResponseLib::sendFail('Falha ao obter token de acesso da B3: ' . $e->getMessage(), [], 'E_B3_TOKEN')->withStatus(502);
- }
- try {
- $result = $this->b3Service->postCpr($token, $payload);
- } catch (\Throwable $e) {
- return ResponseLib::sendFail('Falha ao enviar CPR à B3: ' . $e->getMessage(), [], 'E_EXTERNAL')->withStatus(502);
- }
- if (isset($result['error'])) {
- return ResponseLib::sendFail('cURL error during B3 CPR request', ['error' => $result['error']], 'E_EXTERNAL')->withStatus(502);
- }
- try {
- $tokenResult = $this->createTokenFromCpr($cpr);
- } catch (\Throwable $e) {
- return ResponseLib::sendFail(
- 'Falha ao gerar token: ' . $e->getMessage(),
- [],
- 'E_TOKEN_CREATE'
- )->withStatus(500);
- }
- try {
- $this->cprModel->updateTokenId((int)$cpr['cpr_id'], (int)$tokenResult['token_id']);
- } catch (\Throwable $e) {
- return ResponseLib::sendFail(
- 'Falha ao vincular token à CPR: ' . $e->getMessage(),
- [],
- 'E_CPR_UPDATE'
- )->withStatus(500);
- }
- return ResponseLib::sendOk([
- 'message' => 'CPR enviada e token criado com sucesso',
- 'payment_id' => $paymentId,
- 'b3_response' => $result['json'] ?? ($result['raw'] ?? null),
- 'token_id' => $tokenResult['token_id'],
- 'token_external_id' => $tokenResult['token_external_id'],
- 'tx_hash' => $tokenResult['tx_hash'],
- ], 'S_CPR_SENT');
- }
- private function resolveB3Token(ServerRequestInterface $request, array $body): string
- {
- $token = $body['b3_access_token'] ?? ($body['access_token'] ?? null);
- if (!$token) {
- $b3Auth = $request->getHeaderLine('X-B3-Authorization') ?: '';
- if (stripos($b3Auth, 'Bearer ') === 0) {
- $token = trim(substr($b3Auth, 7));
- }
- }
- if (!$token) {
- $token = $request->getHeaderLine('X-B3-Access-Token') ?: null;
- }
- if (!$token) {
- $token = $this->b3Service->getAccessToken();
- }
- return $token;
- }
- private function createTokenFromCpr(array $cpr): array
- {
- $inputs = $this->prepareTokenInputs($cpr);
- return $this->tokenCreateService->createToken(
- $inputs['token_commodities_amount'],
- $inputs['token_commodities_value'],
- $inputs['token_uf'],
- $inputs['token_city'],
- $inputs['token_content'],
- $inputs['token_flag'],
- $inputs['wallet_id'],
- $inputs['chain_id'],
- $inputs['commodities_id'],
- $inputs['cpr_id'],
- $inputs['user_id']
- );
- }
- /**
- * @return array{
- * token_commodities_amount:int,
- * token_commodities_value:int,
- * token_uf:string,
- * token_city:string,
- * token_content:string,
- * token_flag:string,
- * wallet_id:int,
- * chain_id:int,
- * commodities_id:int,
- * cpr_id:int,
- * user_id:int
- * }
- */
- private function prepareTokenInputs(array $cpr): array
- {
- $cprId = (int)($cpr['cpr_id'] ?? 0);
- if ($cprId <= 0) {
- throw new \InvalidArgumentException('CPR sem identificador válido.');
- }
- $userId = (int)($cpr['user_id'] ?? 0);
- if ($userId <= 0) {
- throw new \InvalidArgumentException('CPR sem usuário associado.');
- }
- $companyId = (int)($cpr['company_id'] ?? 0);
- if ($companyId <= 0) {
- throw new \InvalidArgumentException('CPR sem empresa associada.');
- }
- $wallet = $this->findWalletByCompanyId($companyId);
- $commoditiesName = $this->requireStringField($cpr, ['cpr_product_name'], 'cpr_product_name');
- $commoditiesId = $this->resolveCommodityId($commoditiesName);
- $tokenCommoditiesAmount = $this->requireNumericField(
- $cpr,
- ['cpr_product_quantity', 'cpr_issue_quantity'],
- 'quantidade do produto'
- );
- $tokenCommoditiesValue = $this->requireNumericField(
- $cpr,
- ['cpr_issue_value', 'cpr_issue_financial_value'],
- 'valor do produto'
- );
- $tokenUf = $this->requireStringField(
- $cpr,
- ['cpr_deliveryPlace_state_acronym', 'cpr_issuers_state_acronym'],
- 'UF'
- );
- $tokenCity = $this->requireStringField(
- $cpr,
- ['cpr_deliveryPlace_city_name', 'cpr_issuers_city_name'],
- 'cidade'
- );
- return [
- 'token_commodities_amount' => $tokenCommoditiesAmount,
- 'token_commodities_value' => $tokenCommoditiesValue,
- 'token_uf' => $tokenUf,
- 'token_city' => $tokenCity,
- 'token_content' => (string)$cprId,
- 'token_flag' => '',
- 'wallet_id' => $wallet['wallet_id'],
- 'chain_id' => $wallet['chain_id'],
- 'commodities_id' => $commoditiesId,
- 'cpr_id' => $cprId,
- 'user_id' => $userId,
- ];
- }
- private function findWalletByCompanyId(int $companyId): array
- {
- $stmt = $this->pdo->prepare(
- 'SELECT wallet_id, chain_id
- FROM "wallet"
- WHERE company_id = :company_id
- ORDER BY wallet_id ASC
- LIMIT 1'
- );
- $stmt->execute(['company_id' => $companyId]);
- $wallet = $stmt->fetch(\PDO::FETCH_ASSOC);
- if (!$wallet) {
- throw new \RuntimeException('Nenhuma carteira encontrada para a empresa informada.');
- }
- return [
- 'wallet_id' => (int)$wallet['wallet_id'],
- 'chain_id' => (int)$wallet['chain_id'],
- ];
- }
- private function resolveCommodityId(string $name): int
- {
- $commodityId = $this->commodityModel->getIdByName($name);
- if ($commodityId === null) {
- throw new \RuntimeException('Commodity não encontrada para o produto: ' . $name);
- }
- return $commodityId;
- }
- private function requireStringField(array $cpr, array $candidates, string $label): string
- {
- foreach ($candidates as $field) {
- if (!array_key_exists($field, $cpr)) {
- continue;
- }
- $value = $this->normalizeStringValue($cpr[$field]);
- if ($value !== '') {
- return $value;
- }
- }
- throw new \InvalidArgumentException("Campo {$label} ausente ou inválido na CPR.");
- }
- private function requireNumericField(array $cpr, array $candidates, string $label): int
- {
- foreach ($candidates as $field) {
- if (!array_key_exists($field, $cpr)) {
- continue;
- }
- $value = $this->normalizeNumericValue($cpr[$field]);
- if ($value !== null) {
- return $value;
- }
- }
- throw new \InvalidArgumentException("Campo {$label} ausente ou inválido na CPR.");
- }
- private function normalizeStringValue($value): string
- {
- if (is_array($value)) {
- $value = reset($value);
- }
- if (!is_scalar($value)) {
- return '';
- }
- $stringValue = trim((string)$value);
- if ($stringValue === '') {
- return '';
- }
- $parts = preg_split('/\s*;\s*/', $stringValue) ?: [];
- $first = $parts[0] ?? $stringValue;
- return trim((string)$first);
- }
- private function normalizeNumericValue($value): ?int
- {
- if (is_array($value)) {
- $value = reset($value);
- }
- if (is_string($value)) {
- $value = str_replace([' ', ','], ['', '.'], $value);
- }
- if (is_numeric($value)) {
- return (int)round((float)$value);
- }
- return null;
- }
- }
|