$cpr['cpr_type_code'] ?? null, 'otcRegisterAccountCode' => $cpr['cpr_otc_register_account_code'] ?? null, 'otcCustodianAccountCode' => $cpr['cpr_otc_custodian_account_code'] ?? null, 'electronicEmissionIndicator' => $this->mapIndicator($cpr['cpr_electronic_emission_indicator'] ?? null), 'issueDate' => $this->convertDate($cpr['cpr_issue_date'] ?? null), 'maturityDate' => $this->convertDate($cpr['cpr_maturity_date'] ?? null), 'issueQuantity' => $cpr['cpr_issue_quantity'] ?? null, 'issueValue' => $cpr['cpr_issue_value'] ?? null, 'issueFinancialValue' => $cpr['cpr_issue_financial_value'] ?? null, 'profitabilityStartDate' => $this->convertDate($cpr['cpr_profitability_start_date'] ?? null), 'automaticExpirationIndicator' => $this->mapIndicator($cpr['cpr_automatic_expiration_indicator'] ?? null), 'collaterals' => [[ 'collateralTypeCode' => $cpr['cpr_collateral_type_code'] ?? null, 'collateralTypeName' => $cpr['cpr_collateral_type_name'] ?? null, 'constitutionProcessIndicator' => $this->mapIndicator($cpr['cpr_constitution_process_indicator'] ?? null), 'otcBondsmanAccountCode' => $cpr['cpr_otc_bondsman_account_code'] ?? null, ]], 'products' => [[ 'cprProductName' => $cpr['cpr_product_name'] ?? null, 'cprProductClassName' => $cpr['cpr_product_class_name'] ?? null, 'cprProductHarvest' => $cpr['cpr_product_harvest'] ?? null, 'cprProductDescription' => $cpr['cpr_product_description'] ?? null, 'cprProductQuantity' => $cpr['cpr_product_quantity'] ?? null, 'measureUnitName' => $cpr['cpr_measure_unit_name'] ?? null, 'packagingWayName' => $cpr['cpr_packaging_way_name'] ?? null, 'cprProductStatusCode' => $cpr['cpr_product_status_code'] ?? null, 'productionTypeCode' => $cpr['cpr_production_type_code'] ?? null, ]], 'deliveryPlace' => [ 'documentDeadlineDaysNumber' => $cpr['cpr_document_deadline_days_number'] ?? null, 'placeName' => $cpr['cpr_place_name'] ?? null, 'stateAcronym' => $cpr['cpr_deliveryPlace_state_acronym'] ?? null, 'cityName' => $cpr['cpr_deliveryPlace_city_name'] ?? null, ], 'issuers' => [[ 'cprIssuerName' => $cpr['cpr_issuer_name'] ?? null, 'documentNumber' => $cpr['cpr_issuers_document_number'] ?? null, 'personTypeAcronym' => $cpr['cpr_issuers_person_type_acronym'] ?? null, 'issuerLegalNatureCode' => $cpr['cpr_issuer_legal_nature_code'] ?? null, 'stateAcronym' => $cpr['cpr_issuers_state_acronym'] ?? null, 'cityName' => $cpr['cpr_issuers_city_name'] ?? null, ]], 'scr' => [ 'scrTypeCode' => $cpr['cpr_scr_type_code'] ?? null, 'finalityCode' => $cpr['cpr_finality_code'] ?? null, 'contractCode' => $cpr['cpr_contract_code'] ?? null, ], 'creditor' => [ 'creditorName' => $cpr['cpr_creditor_name'] ?? null, 'documentNumber' => $cpr['cpr_creditor_document_number'] ?? null, ], 'productionPlaces' => [[ 'productionPlaceName' => $cpr['cpr_production_place_name'] ?? null, 'propertyRegistrationNumber' => $cpr['cpr_property_registration_number'] ?? null, 'notaryName' => $cpr['cpr_notary_name'] ?? null, 'totalProductionAreaInHectaresNumber' => $cpr['cpr_total_production_area_in_hectares_number'] ?? null, 'totalAreaInHectaresNumber' => $cpr['cpr_total_area_in_hectares_number'] ?? null, 'carCode' => $cpr['cpr_car_code'] ?? null, 'latitudeCode' => $cpr['cpr_latitude_code'] ?? null, 'longitudeCode' => $cpr['cpr_longitude_code'] ?? null, 'zipCode' => $cpr['cpr_zip_code'] ?? null, ]], ]; return ['data' => ['instrument' => $instrument]]; } private function convertDate(?string $date): ?string { if (!is_string($date) || trim($date) === '') { return null; } $t = trim($date); if (mb_strtoupper($t, 'UTF-8') === 'NA') { return null; } $dt = \DateTime::createFromFormat('Y-m-d', $t); if ($dt instanceof \DateTime) { return $dt->format('d/m/Y'); } $alt = \DateTime::createFromFormat('d/m/Y', $t); if ($alt instanceof \DateTime) { return $t; } return $t; } private function mapIndicator($value): ?string { if (!is_string($value)) { return null; } $v = mb_strtoupper(trim($value), 'UTF-8'); if ($v === 'SIM' || $v === 'S') { return 'S'; } if ($v === 'N' || $v === 'NAO' || $v === 'NÃO') { return 'N'; } if ($v === 'NA') { return null; } return $value; } public function sanitize(array $payload): array { return $this->removeNulls($payload); } private function removeNulls($value) { if (is_array($value)) { $isAssoc = $this->isAssoc($value); $result = []; foreach ($value as $k => $v) { if (is_string($v) && mb_strtoupper(trim($v), 'UTF-8') === 'NA') { $v = null; } $v = $this->removeNulls($v); if (is_array($v) && count($v) === 0) { continue; } $result[$k] = $v; } if (!$isAssoc) { $result = array_values($result); } return $result; } if (is_string($value) && mb_strtoupper(trim($value), 'UTF-8') === 'NA') { return null; } return $value; } private function isAssoc(array $arr): bool { return array_keys($arr) !== range(0, count($arr) - 1); } public function postCpr(string $accessToken, array $payload): array { $url = $_ENV['B3_URL_CPR'] ?? null; if (!$url) { throw new \RuntimeException('B3_URL_CPR not configured'); } $json = json_encode($this->sanitize($payload), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($json === false) { throw new \RuntimeException('Failed to encode payload'); } $certPath = dirname(__DIR__) . DIRECTORY_SEPARATOR . '319245319-320109623_PROD.cer'; $keyPath = dirname(__DIR__) . DIRECTORY_SEPARATOR . '319245319-320109623_PROD.key'; $certPass = $_ENV['CERT_PASS'] ?? null; if (!file_exists($certPath) || !file_exists($keyPath)) { throw new \RuntimeException('Client certificate or key not found'); } $ch = curl_init($url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); $headers = [ 'Accept: application/json', 'Content-Type: application/json', 'Authorization: Bearer ' . $accessToken, ]; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $json); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSLCERT, $certPath); curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM'); curl_setopt($ch, CURLOPT_SSLKEY, $keyPath); curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM'); if ($certPass) { curl_setopt($ch, CURLOPT_SSLKEYPASSWD, $certPass); } curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); $responseBody = curl_exec($ch); $curlErrNo = curl_errno($ch); $curlErr = curl_error($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($curlErrNo !== 0) { return ['error' => $curlErr, 'status' => 0]; } $decoded = json_decode((string)$responseBody, true); if ($decoded === null) { return ['raw' => $responseBody, 'status' => $httpCode ?: 502]; } return ['json' => $decoded, 'status' => $httpCode ?: 200]; } }