| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- <?php
- namespace Services;
- class B3CprService
- {
- public function getAccessToken(): string
- {
- $b3Url = $_ENV['B3_URL'] ?? null;
- $grantType = $_ENV['GRANT_TYPE'] ?? 'client_credentials';
- $certPass = $_ENV['CERT_PASS'] ?? null;
- if (!$b3Url || !$grantType) {
- throw new \RuntimeException('Missing required env vars: B3_URL, GRANT_TYPE');
- }
- $certPath = dirname(__DIR__) . DIRECTORY_SEPARATOR . '319245319-320109623_PROD.cer';
- $keyPath = dirname(__DIR__) . DIRECTORY_SEPARATOR . '319245319-320109623_PROD.key';
- if (!file_exists($certPath) || !file_exists($keyPath)) {
- throw new \RuntimeException('Client certificate or key not found');
- }
- $postFields = 'grant_type=' . ($grantType);
- $ch = curl_init($b3Url);
- curl_setopt($ch, CURLOPT_POST, true);
- $headers = [
- 'Content-Type: application/x-www-form-urlencoded',
- 'Accept: application/json',
- ];
- curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
- 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) {
- throw new \RuntimeException('cURL error: ' . $curlErr);
- }
- $decoded = json_decode((string)$responseBody, true);
- if (!is_array($decoded)) {
- throw new \RuntimeException('Invalid token response (non-JSON), HTTP ' . $httpCode);
- }
- $token = $decoded['access_token'] ?? null;
- if (!is_string($token) || $token === '') {
- throw new \RuntimeException('access_token not present in token response');
- }
- return $token;
- }
- public function mapToB3(array $cpr): array
- {
- $collateralTypeCodes = $this->splitSemicolonList($cpr['cpr_collateral_type_code'] ?? null);
- $collateralTypeNames = $this->splitSemicolonList($cpr['cpr_collateral_type_name'] ?? null);
- $constitutionProcessIndicators = $this->splitSemicolonList($cpr['cpr_constitution_process_indicator'] ?? null);
- $otcBondsmanAccountCodes = $this->splitSemicolonList($cpr['cpr_otc_bondsman_account_code'] ?? null);
- $collateralsCount = max(
- count($collateralTypeCodes),
- count($collateralTypeNames),
- count($constitutionProcessIndicators),
- count($otcBondsmanAccountCodes)
- );
- if ($collateralsCount <= 0) {
- $collateralsCount = 1;
- }
- $collaterals = [];
- for ($i = 0; $i < $collateralsCount; $i++) {
- $collaterals[] = [
- 'collateralTypeCode' => $collateralTypeCodes[$i] ?? null,
- 'collateralTypeName' => $collateralTypeNames[$i] ?? null,
- 'constitutionProcessIndicator' => $this->mapIndicator($constitutionProcessIndicators[$i] ?? null),
- 'otcBondsmanAccountCode' => $otcBondsmanAccountCodes[$i] ?? null,
- ];
- }
- $issuerNames = $this->splitSemicolonList($cpr['cpr_issuer_name'] ?? null);
- $issuerDocumentNumbers = $this->splitSemicolonList($cpr['cpr_issuers_document_number'] ?? null);
- $issuerPersonTypes = $this->splitSemicolonList($cpr['cpr_issuers_person_type_acronym'] ?? null);
- $issuerLegalNatures = $this->splitSemicolonList($cpr['cpr_issuer_legal_nature_code'] ?? null);
- $issuerStates = $this->splitSemicolonList($cpr['cpr_issuers_state_acronym'] ?? null);
- $issuerCities = $this->splitSemicolonList($cpr['cpr_issuers_city_name'] ?? null);
- $issuersCount = max(
- count($issuerNames),
- count($issuerDocumentNumbers),
- count($issuerPersonTypes),
- count($issuerLegalNatures),
- count($issuerStates),
- count($issuerCities)
- );
- if ($issuersCount <= 0) {
- $issuersCount = 1;
- }
- $issuers = [];
- for ($i = 0; $i < $issuersCount; $i++) {
- $issuers[] = [
- 'cprIssuerName' => $issuerNames[$i] ?? null,
- 'documentNumber' => $issuerDocumentNumbers[$i] ?? null,
- 'personTypeAcronym' => $issuerPersonTypes[$i] ?? null,
- 'issuerLegalNatureCode' => $issuerLegalNatures[$i] ?? null,
- 'stateAcronym' => $issuerStates[$i] ?? null,
- 'cityName' => $issuerCities[$i] ?? null,
- ];
- }
- $productionPlaceNames = $this->splitSemicolonList($cpr['cpr_production_place_name'] ?? null);
- $propertyRegistrationNumbers = $this->splitSemicolonList($cpr['cpr_property_registration_number'] ?? null);
- $notaryNames = $this->splitSemicolonList($cpr['cpr_notary_name'] ?? null);
- $totalProductionAreas = $this->splitSemicolonList($cpr['cpr_total_production_area_in_hectares_number'] ?? null);
- $totalAreas = $this->splitSemicolonList($cpr['cpr_total_area_in_hectares_number'] ?? null);
- $carCodes = $this->splitSemicolonList($cpr['cpr_car_code'] ?? null);
- $latitudeCodes = $this->splitSemicolonList($cpr['cpr_latitude_code'] ?? null);
- $longitudeCodes = $this->splitSemicolonList($cpr['cpr_longitude_code'] ?? null);
- $zipCodes = $this->splitSemicolonList($cpr['cpr_zip_code'] ?? null);
- $productionPlacesCount = max(
- count($productionPlaceNames),
- count($propertyRegistrationNumbers),
- count($notaryNames),
- count($totalProductionAreas),
- count($totalAreas),
- count($carCodes),
- count($latitudeCodes),
- count($longitudeCodes),
- count($zipCodes)
- );
- if ($productionPlacesCount <= 0) {
- $productionPlacesCount = 1;
- }
- $productionPlaces = [];
- for ($i = 0; $i < $productionPlacesCount; $i++) {
- $productionPlaces[] = [
- 'productionPlaceName' => $productionPlaceNames[$i] ?? null,
- 'propertyRegistrationNumber' => $propertyRegistrationNumbers[$i] ?? null,
- 'notaryName' => $notaryNames[$i] ?? null,
- 'totalProductionAreaInHectaresNumber' => $totalProductionAreas[$i] ?? null,
- 'totalAreaInHectaresNumber' => $totalAreas[$i] ?? null,
- 'carCode' => $carCodes[$i] ?? null,
- 'latitudeCode' => $latitudeCodes[$i] ?? null,
- 'longitudeCode' => $longitudeCodes[$i] ?? null,
- 'zipCode' => $zipCodes[$i] ?? null,
- ];
- }
- $instrument = [
- 'cprTypeCode' => $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' => $collaterals,
- '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' => $issuers,
- 'scr' => [
- 'scrTypeCode' => $cpr['cpr_scr_type_code'] ?? null,
- 'finalityCode' => $cpr['cpr_finality_code'] ?? null,
- 'contractCode' => $cpr['cpr_contract_code'] ?? null,
- ],
- 'deposit' => [
- 'otcFavoredAccountCode' => '64359.00-3',
- 'selfNumber' => $cpr['cpr_contract_code'] ?? null,
- 'settlementModalityTypeCode' => '0',
- 'depositQuantity' => '1',
- ],
- 'creditor' => [
- 'creditorName' => $cpr['cpr_creditor_name'] ?? null,
- 'documentNumber' => $cpr['cpr_creditor_document_number'] ?? null,
- ],
- 'productionPlaces' => $productionPlaces,
- ];
- return ['data' => ['instrument' => $instrument]];
- }
- private function splitSemicolonList($value): array
- {
- if (is_array($value)) {
- $items = array_map(static fn($v) => trim((string)$v), array_values($value));
- return array_values(array_filter($items, static fn($v) => $v !== ''));
- }
- if (!is_string($value)) {
- return [];
- }
- $trimmed = trim($value);
- if ($trimmed === '') {
- return [];
- }
- $parts = preg_split('/\s*;\s*/', $trimmed) ?: [];
- $parts = array_map(static fn($v) => trim((string)$v), $parts);
- return array_values(array_filter($parts, static fn($v) => $v !== ''));
- }
- 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];
- }
- }
|