✏️ 正在编辑: Transfeera.php
路径:
/srv/systems_dir/koruspay/library/Core/Service/Transfeera.php
提示:
您可以编辑任何文件(包括二进制文件),但请注意不当修改可能导致文件损坏。
<?php class Core_Service_Transfeera { // private $_url = "https://api-sandbox.transfeera.com"; private $_url = "https://api.transfeera.com"; private $_clientId; private $_clientSecret; private $_accountId; private $_companyId; private $_token; private $_loteId; const NOME_API = 'TRANSFEERA'; public function __construct($companyId = null) { $config = new Zend_Config_Ini(CLIENT_PATH . "/configs/application.ini"); $chaves = $config->production->transfeera; if (empty($chaves->client_id) || empty($chaves->client_secret)) { throw new Core_Exception("Dados de integração com a Transfeera não estão configurados." ); } $this->_clientId = $chaves->client_id; $this->_clientSecret = $chaves->client_secret; $company = Model_Container::getCompany()->findById($companyId); if (!empty($company->access_key)) { $credentials = $this->getAccessKey($company->access_key); $this->_clientId = $credentials->client_id; $this->_clientSecret = $credentials->client_secret; } $this->_companyId = $companyId; $this->_loteId = 0; } protected function getAccessKey(string $accessKey) { $hash = Yuppie_Crypt::Decrypt($accessKey); $keys = explode(':', $hash); $credentials = new stdClass(); $credentials->client_id = $keys[0]; $credentials->client_secret = $keys[1]; return $credentials; } public function getLoteId() { return $this->_loteId; } public function token() { $this->newToken(); return $this->_token; } protected function newToken() { $params = [ 'grant_type' => 'client_credentials', 'client_id' => $this->_clientId, 'client_secret' => $this->_clientSecret ]; $curl = curl_init(); $url = "https://login-api.transfeera.com/authorization"; curl_setopt_array($curl, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => json_encode($params), CURLOPT_HTTPHEADER => [ "User-Agent: KorusPay (guilherme@koruspay.com)", "Accept: application/json", "Content-type: application/json" ], ]); $response = curl_exec($curl); curl_close($curl); $token = json_decode($response); Yuppie_Log::write("Gerado novo token: " . print_r($response, 1), $this); if (empty($token->access_token)) { Yuppie_Log::write("Erro na geração do novo token: " . print_r($response, 1), $this); throw new Exception('Erro na geração do novo token.'); } $this->_token = $token->access_token; $expiraEm = Zend_Date::now()->addSecond($token->expires_in); (new Model_DbTable_TokenApi())->saveToken(self::NOME_API, $this->_token, $expiraEm->get('yyyy-MM-dd HH:mm:ss')); Yuppie_Log::write("Novo token gerado. Expira em: " . $expiraEm->get('yyyy-MM-dd HH:mm:ss'), $this); } protected function getResponse($endPoint, $params, $method = 'POST') { $url = $this->_url . $endPoint; $options = [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ "User-Agent: KorusPay (contato@koruspay.com.br)", "Accept: application/json", "Authorization: Bearer {$this->_token}", "Content-type: application/json" ], ]; if ($method == 'POST') { $options[CURLOPT_POSTFIELDS] = json_encode($params); } $curl = curl_init(); curl_setopt_array($curl, $options); $response = curl_exec($curl); $json = json_decode($response); $info = curl_getinfo($curl); $codeErros = [400, 401, 403, 429]; curl_close($curl); Yuppie_Log::write("getResponse Transfeera, parâmetros enviados para url {$url}:" . print_r($params, true), $this); Yuppie_Log::write("Retorno do getResponse Transfeera: Code HTTP - " . $info['http_code'] . ' - ' . print_r($json, true), $this); Model_RequestsHistory::saveRequest(json_encode($params), 'output', $this->_companyId, $url); Model_RequestsHistory::saveRequest($response, 'input', $this->_companyId, $url); return $json; } protected function getTokenApi() { $tokenValido = (new Model_DbTable_TokenApi())->checkTokenValido(self::NOME_API, $this->_companyId); if ($tokenValido) { $this->_token = $tokenValido['token']; return; } $this->newToken(); } protected function _cleanPixDescription($text) { // 1. Remove acentos (ex: ó -> o, ç -> c) $text = preg_replace('~[\p{M}]~u', '', normalizer_normalize($text, Normalizer::FORM_D)); // 2. Remove tudo que não for letra (A-Z), número (0-9) ou espaço // Isso elimina o símbolo "°", hashtags, emojis, etc. $text = preg_replace('/[^a-zA-Z0-9 ]/', '', $text); // 3. Remove espaços duplos e limita a 140 caracteres (padrão Pix) $text = trim(preg_replace('/\s+/', ' ', $text)); return substr($text, 0, 140); //há um limite de 140 caracteres na documentação } public function createInvoicePix($invoice) { if (empty($this->_token)) { $this->getTokenApi(); } $endPoint = '/pix/qrcode/collection/immediate'; $params = [ 'integration_id' => $invoice['id'], 'pix_key' => (new Model_DbTable_Settings())->fetchRow()->pix_key, 'original_value' => $invoice['amount'], 'payer_question' => $invoice['description'], 'expiration' => 86400, // 24h 'value_change_mode' => 'VALOR_FIXADO', ]; $response = $this->getResponse($endPoint, $params); if ($response) { return $response; } throw new Exception('Houve um erro na geração do QR Code'); } public function getInvoicePixDetail(string $id) { if (empty($this->_token)) { $this->getTokenApi(); } $endPoint = "/pix/qrcode/{$id}"; $response = $this->getResponse($endPoint, [], 'GET'); return $response; } public function createInvoiceBankSlip(array $invoice) { if (empty($this->_token)) { $this->getTokenApi(); } $settings = (new Model_DbTable_Settings())->fetchRow(); $endPoint = "/charges"; $params = [ 'payment_methods' => [ 'boleto' ], 'payment_method_details' => [ 'boleto' => [ 'type' => 'other' ] ], 'payer' => [ 'name' => $invoice['payer'], 'trade_name' => $invoice['payer'], 'tax_id' => $invoice['doc_number'], 'address' => [ 'street' => $invoice['payer_street'], 'number' => $invoice['payer_number'], 'complement' => $invoice['payer_complement'], 'district' => $invoice['payer_neighborhood'], 'city' => $invoice['payer_city'], 'state' => $invoice['payer_state'], 'postal_code' => $invoice['payer_zip_code'] ] ], "amount" => (int)String_Refatorar::soNumeros($invoice['amount']), // o valor deve ser inteiro, sem casas décimais ex.: R$ 78,52 = 7852 'due_date' => $invoice['due_date'], 'expiration_date' => $invoice['expiration_date'] ?: (new Zend_Date($invoice['due_date']))->addDay(10)->get('yyyy-MM-dd'), "description" => $invoice['description'] ?: "Boleto", 'external_id' => $invoice['id'], 'split_payment' => [[ 'mode' => 'fixed', 'amount' => $invoice['amount_tax'] > 0 ? (int)String_Refatorar::soNumeros($invoice['amount_tax']) : 100, 'receiver' => [ 'pix_key' => $settings->pix_key, ], // 'payment_date' => $invoice['due_date'], 'split_days_after_settled' => 1 ]], ]; $discount = [ "discount_amount" => (int)String_Refatorar::soNumeros($invoice['discount']), 'discount_dates' => [ [ 'date' => $invoice['due_date'], 'percent' => $invoice['discount'], 'amount' => $invoice['discount'] ? (int)String_Refatorar::soNumeros(round(($invoice['discount']*$invoice['amount']/100), 2)) : null // o valor deve ser inteiro, sem casas décimais ex.: R$ 78,52 = 7852 ] ], 'discount_type' => 'fixed_until_informed_dates' ]; $fine = [ "fine_amount" => $invoice['penalty'] ? (int)String_Refatorar::soNumeros(round(($invoice['penalty']*$invoice['amount']/100), 2)) : null, // o valor deve ser inteiro, sem casas décimais ex.: R$ 78,52 = 7852 "fine_percent" => $invoice['penalty'] ?: null, "fine_type" => "PERCENTUAL", ]; $interest = [ "interest_amount" => $invoice['fees'] ? (int)String_Refatorar::soNumeros(round(($invoice['fees']*$invoice['amount']/100), 2)) : null, // o valor deve ser inteiro, sem casas décimais ex.: R$ 78,52 = 7852, "interest_percent" => $invoice['fees'] ?: null, "interest_type" => "percentage_per_month", ]; if (!empty($invoice['discount'])) { array_push($params, $discount); } if (!empty($invoice['penalty'])) { array_push($params, $fine); } if (!empty($invoice['fees'])) { array_push($params, $interest); } if (!empty($invoice['company_pix_key'])) { array_push($params['payment_methods'], 'pix'); $params['payment_method_details']['pix'] = [ 'pix_key' => $invoice['company_pix_key'] ]; } $response = $this->getResponse($endPoint, $params); if ($response->message) { $return = new stdClass(); $return->error = $response->message; return $return; } return current($response->receivables) ?: []; } public function createTransfer(array $transfer, $tax = false) { if (empty($this->_token)) { $this->getTokenApi(); } if (empty($tax) && empty($transfer['pix_key'])) { throw new Exception('Chave Pix é obrigatória para transferência.'); } $loteId = null; if (!empty($transfer['number'])) { $loteId = $this->findLote($transfer['number']); } if (!$loteId) { $loteId = $this->_createLote("Recebimento lote {$transfer['split_id']}"); } $this->_loteId = $loteId; (new Model_DbTable_Split())->update(['number' => $loteId], 'id = ' . $transfer['split_id']); return $this->_populateLote($transfer); } public function validarChavePix(array $chave) { $endPoint = '/pix/dict_key/' . $chave['pix'] . '?key_type=' . $chave['type_pix']; $response = $this->getResponse($endPoint, [], 'GET'); if (empty($response->end2end_id)) { return false; } return $response->end2end_id; } protected function _createLote($name) { if (empty($this->_token)) { $this->getTokenApi(); } $endPoint = '/batch'; $params = [ 'type' => 'TRANSFERENCIA', 'auto_close' => false, 'name' => $name ]; $response = $this->getResponse($endPoint, $params); if ($response->id) { return $response->id; } throw new Exception('Houve um erro na criação do Lote'); } protected function _populateLote(array $transfer) { if (empty($this->_token)) { $this->getTokenApi(); } if (empty($transfer['id'])) { throw new Exception('ID da transação é obrigatório para criar a transferência.'); } $endPoint = "/batch/{$this->_loteId}/transfer"; $params = [ 'value' => abs($transfer['amount']), 'integration_id' => $transfer['id'], 'idempotency_key' => "transfer_id-" . $transfer['id'], 'pix_description' => $this->_cleanPixDescription($transfer['description']) ]; if (empty($transfer['pix_key']) && $transfer['type_pix'] != "DADOS_BANCARIOS") { throw new Exception('Chave Pix é obrigatória para transferência ou informe os dados bancários com o tipo de chave DADOS_BANCARIOS.'); } if ($transfer['type_pix'] == "DADOS_BANCARIOS") { $dataAccount = String_Refatorar::soNumeros($transfer['account_number']); $accountNumber = substr($dataAccount, 0, -1); $accountDigit = substr($dataAccount, -1); $params['destination_bank_account'] = [ 'account_type' => $transfer['account_type'], //CONTA_CORRENTE, CONTA_POUPANCA, CONTA_PAGAMENTO, CONTA_FACIL, ENTIDADES_PUBLICAS 'name' => $transfer['name_holder'], 'cpf_cnpj' => $transfer['doc_holder'], 'bank_code' => $transfer['bank_code'], 'agency' => $transfer['branch_code'], 'account' => $accountNumber, 'account_digit' => $accountDigit ]; } else { // $end2end = $this->validarChavePix($item); // if (!$end2end) { // $itensErro[$item['id']] = ['id' => $item['id'], 'situation' => 'ERRO', 'deleted' => 1]; // continue; // } // $params['pix_end2end_id'] = $end2end; $params['destination_bank_account'] = [ 'pix_key_type' => stristr($transfer['type_pix'], 'aleatoria') ? "CHAVE_ALEATORIA" : $transfer['type_pix'], 'pix_key' => $transfer['pix_key'], ]; } return $this->getResponse($endPoint, $params); } public function closeLote($loteId) { if (empty($this->_token)) { $this->getTokenApi(); } if (empty($loteId)) { throw new Exception('Lote ID é obrigatório para fechar o lote.'); } $endPoint = "/batch/{$loteId}/close"; $response = $this->getResponse($endPoint, []); if ($response->errorCode == 'BAT_1') { return false; } return true; } public function findLote($loteId) { if (empty($this->_token)) { $this->getTokenApi(); } $endPoint = "/batch/{$loteId}"; $response = $this->getResponse($endPoint, [], "GET"); return !empty($response->id) ? $response->id : false; } public function balance() { if (empty($this->_token)) { $this->getTokenApi(); } $endPoint = "/statement/balance"; $response = $this->getResponse($endPoint, [], 'GET'); if (empty($response->value)) { return false; } return $response->value; } public function createWithdraw($balance) { if (empty($this->_token)) { $this->getTokenApi(); } $endPoint = "/statement/withdraw"; $response = $this->getResponse($endPoint, [ 'value' => abs($balance) ]); if (!empty($response->error)) { throw new Exception('Houve um erro na criação do saque: ' . ($response->message ?: 'Resposta inesperada da API')); } return $response; } }
💾 保存文件
← 返回文件管理器