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); } $tickerSymbol = $result['json']['data']['tickerSymbol'] ?? null; 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']); if ($tickerSymbol !== null) { $this->cprModel->updateTicker((int)$cpr['cpr_id'], (string)$tickerSymbol); } } catch (\Throwable $e) { return ResponseLib::sendFail( 'Falha ao atualizar 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 = 2; $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; } }