Skip to content

Instantly share code, notes, and snippets.

@bonomali
Created March 15, 2020 21:12
Show Gist options
  • Save bonomali/9912785c3ae8445419c0e430e9294689 to your computer and use it in GitHub Desktop.
Save bonomali/9912785c3ae8445419c0e430e9294689 to your computer and use it in GitHub Desktop.
Integração API Boletos - PHP
<?php
namespace safra\src;
use Crypt_RSA;
use Exception;
use DateTime;
use GuzzleHttp\json_encode;
include("Crypt/RSA.php");
include("random/lib/random.php");
class Safra
{
private $privateKeyContent;
private $uniqueIdSession;
private $kidDecoded;
private $uniqueIdMessage;
private $token;
private $ivBase64;
private $blocksize = 32;
//CONSTRUTOR
public function __construct($environment)
{
//constroi o ambiente
$this->environment = '';
if ($environment == 'H') {
$this->environment = '-hml';
}
//url para troca de chaves
$this->psd001 = 'https://cobranca' . $this->environment . '.safranegocios.com.br/psd/psd001';
// url Autenticação do cliente com disponibilização de token (JWT) e identificador de sessão de autenticação
$this->psd005 = 'https://cobranca' . $this->environment . '.safranegocios.com.br/psd/psd005';
//url para renovação de token
$this->psd007 = 'https://cobranca' . $this->environment . '.safranegocios.com.br/psd/psd007';
//url Inclusão de boletos
$this->psd050 = 'https://cobranca' . $this->environment . '.safranegocios.com.br/psd/psd050';
//url - consulta de boletos
$this->psd052 = 'https://cobranca' . $this->environment . '.safranegocios.com.br/psd/psd052';
$this->startServices();
}
private function startServices()
{
$keyHandshake = $this->executeHandshake();
$keyHandshake1 = $this->executeHandshake1($keyHandshake);
$keyHandshake2 = $this->executeHandshake2($keyHandshake1);
$keyAuthorization = $this->executeAuthorization($keyHandshake2);
$this->executeGenerateBoleto($keyAuthorization);
}
//EXECUTE
private function executeHandshake()
{
$publicKeyContent = "-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmTZZN3ghnI8D10/IXO8X\r\nY37MU5F3EfHNUBOPHgm9ANVbIkqDd+2sGIse22R4dwms+a968oEjTkq5rTT49DU2\r\nEeD3+5q2ZNGmyKpUBUn4g7xCJXU13QaiEzKrP7Xjh3F/8e1+6SpMzoa68sONbvWF\r\n289rXU272bxW+kno0HN0lL9zQTJcTmXXaBUDQVOUJNcwMrLpUjcW/iUXJJqUP+R8\r\nM6DvK3bRJD3koBRIzBgv+xS165KNE+5pEy68iEGDyQiOkL5XDKWhLhDJiPbsb7xM\r\nYgNASF+/dQN2KOPmlL55lvyygkxxUaFMNZhxM1X/1q7R1q7m2Ex6kJ7uPhuD90BC\r\nHwIDAQAB\r\n-----END PUBLIC KEY-----";
$this->privateKeyContent = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAmTZZN3ghnI8D10/IXO8XY37MU5F3EfHNUBOPHgm9ANVbIkqD\nd+2sGIse22R4dwms+a968oEjTkq5rTT49DU2EeD3+5q2ZNGmyKpUBUn4g7xCJXU1\n3QaiEzKrP7Xjh3F/8e1+6SpMzoa68sONbvWF289rXU272bxW+kno0HN0lL9zQTJc\nTmXXaBUDQVOUJNcwMrLpUjcW/iUXJJqUP+R8M6DvK3bRJD3koBRIzBgv+xS165KN\nE+5pEy68iEGDyQiOkL5XDKWhLhDJiPbsb7xMYgNASF+/dQN2KOPmlL55lvyygkxx\nUaFMNZhxM1X/1q7R1q7m2Ex6kJ7uPhuD90BCHwIDAQABAoIBAQCAhwk88TgxIAB2\n5WUOITybqMk76bHbid9Up7PN3RmC03kKQTwcpXNTe5G9NoGgSqujIkSfnxnBGgq9\ndMzMAL+FLBacKWONHBswB0NI1I735DwRNEylSGU6ea0TW0CuZNaSW0u1ylh4gKnf\n8ZB6XtmWVmCY+xb1R3wnp0z5YYGRLeXhW6GNaqCn1UGj2/lTFkFuUbgcTTDDHZ6/\nP4vl5eURFsjAw+IsX9timeqR/9i2uPcePuZevARYvYPYtqmuPjfXYFRyO9DIbDQz\n1IsRpVhFhCGOXfybi6Cu0icZWwaJE58clfPtCaod1b9PGzDgE8l09D32pl55nHyu\nsfHppDtRAoGBAMqp7KI0N2cglheKYea2PUUgOUCsUXIlAJS765rldlpwEYa9QG00\nV3vYVXD0Xf/5NuCpxQNuMxuNcD+IbpOl7/hGpbHgaAplvXQW1LKsQehoVXMR30wE\nJ6UjTsN5h9a6dWMugQ0CTlfO0a1G4j2NS/qt3Ktm7JqvzGimyKi+FrjjAoGBAMGI\numADt9InoxW6llAv6yuPjODg8+8k+Z/hQ7QkmmQmgA+nTm6D6JIscFvL3sFxYTNK\nzuwkNQtSy1i3LB2gQF77hfNXi0DONOWGNWpIUG1nugF2/F8+WEf9rnC/4c5l/Cgq\nfLleVpTRKqr63bhhmKlRfd9YIp5v6Mcxw7TKjaKVAoGBALmM+HXN/wexoxnsfhRA\nuDoo9aJL4snyLOUAZQdNnZ3Ry4okje8uJAbkm92uLr4lC2SIqzOn2q0YQzQ4Ep2m\nKN6UuDHytu8GFX5LRPRNmI5TsCCJtXsgtreQaxW3mHI2BzIGqushOF9WoUzXgF5A\nltbuBgL8WhaKbElIoqE0YRNnAoGAO1dwRkZ9k7vBnsUCl/3Jbybp/H4dbcN5oxNQ\nTik3CeRgOeyD+RELO97SaOM/HzNjqXWTswZ7GUi+vyoTJdZgRn97GB1Mvoc74vDe\n6gLYXmOCvjY8tfuI2DIpzbqYQ1A0HCDC85fAlor4OUsItyN9ytUI8nW0z73is0lI\nB+Rfv20CgYEAwGAtvMJG1380dd97g/Tzlv0xWUnITLyrHYd6DayaFTxym5KXC+Wi\n29CZ7L/l4ycbn7gBgLylunylnyvc2/g52OIEDuQj0D5uVujvTNqiJQavINZElKeV\nAArF91s/yxGrxffqe1TJqHeqan7EEQq/7fU2z0v9h4Ip1HQUmFtCQ2Q=\n-----END RSA PRIVATE KEY-----";
$this->uniqueIdSession = self::generateUuid('');
$this->uniqueIdMessage = self::generateUuid('');
$sessionSignature = $this->generateSignatureFromCertificate($this->privateKeyContent, $this->uniqueIdSession);
$body = json_encode([
'jwk' => base64_encode($sessionSignature) . '.' .
base64_encode('RSA') . '.' .
base64_encode('enc') . '.' .
base64_encode('P-256') . '.' .
base64_encode($publicKeyContent),
], JSON_UNESCAPED_SLASHES);
$header = [
'Content-Type: application/json',
'accept-language: pt-BR',
'amc-aplicacao: PSD',
'amc-session-id: ' . $this->uniqueIdSession,
'amc-message-id: ' . $this->uniqueIdMessage,
];
$options1 = array(
CURLOPT_URL => $this->psd001,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $header,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_SSLVERSION => 6,
CURLOPT_CAINFO => realpath("cert/ca-safraSafra.cer"),
CURLOPT_CAPATH => realpath("cert/safr.combo.pem"),
CURLOPT_SSLKEY => realpath("cert/safr.key"),
CURLOPT_PORT => 443,
);
$curl = curl_init();
curl_setopt_array($curl, $options1);
$response = curl_exec($curl);
$error = curl_error($curl);
if ($error) {
new Exception('Erro ao executar handshake 2 com safra');
}
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
echo 'http code <b>' . $httpCode . '</b> handshake com safra <br>';
return json_decode($response);
}
//HANDSHAKE 1
private function executeHandshake1($key)
{
//PASSO 8
$serverPubKey = base64_decode(explode('.', $key->jwk)[3]);
$rsa = new Crypt_RSA();
$rsa->loadKey($serverPubKey);
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setHash('sha256');
$sessionIdEncryptedWithPublicSafraKey = $rsa->encrypt($this->uniqueIdSession);
//Passo 9
$sessionIdSignedWithPublicSafraKey = $this->generateSignatureFromCertificate($this->privateKeyContent, $this->uniqueIdSession);
$body = json_encode([
'parametros' => base64_encode($sessionIdEncryptedWithPublicSafraKey) . '.' . base64_encode($sessionIdSignedWithPublicSafraKey),
], JSON_UNESCAPED_SLASHES);
$this->uniqueIdMessage = self::generateUuid('');
$header = [
'Content-Length: ' . \strlen($body),
'Content-Type: application/json',
'accept-language: pt-BR',
'amc-aplicacao: PSD',
'amc-session-id: ' . $this->uniqueIdSession,
'amc-message-id: ' . $this->uniqueIdMessage,
];
$options2 = array(
CURLOPT_URL => $this->psd001,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $header,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_USERAGENT => false,
CURLOPT_PORT => 443,
CURLOPT_CAINFO => realpath("cert/ca-safra.cer"),
CURLOPT_SSLCERT => realpath("cert/safr.combo.pem"),
CURLOPT_SSLKEY => realpath("cert/safr.key")
);
$curl = curl_init();
curl_setopt_array($curl, $options2);
$response = curl_exec($curl);
$error = curl_error($curl);
if ($error) {
new Exception('Erro ao executar handshake 2 com safra');
}
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
echo 'http code <b>' . $httpCode . '</b> handshake 1 com safra <br>';
return json_decode($response);
}
//HANDSHAKE 2
private function executeHandshake2($key)
{
$jwkEncrypted = base64_decode(explode('.', $key)[0]);
$rsa = new Crypt_RSA();
$rsa->loadKey($this->privateKeyContent);
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setHash('sha256');
$jwkDecrypted = json_decode($rsa->decrypt($jwkEncrypted));
$this->kidDecoded = base64_decode($jwkDecrypted->jwk->kid);
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-ctr'));
$iv = substr(bin2hex($iv), 0, 16);
$this->ivBase64 = base64_encode($iv);
$data = json_encode([
'idEstrategia' => 'autenticar/psd',
'versaoAplicacao' => 1,
'credenciais' => [],
'cliente' => 'BANCO SAFRA SA',
'parceiro' => 'BANCO SAFRA SA',
'userId' => 'BANCO SA'
], JSON_FORCE_OBJECT);
$cabecalho = "{ alg: 'RSA-AEP', enc: 'A256CTR' }";
$hash128 = openssl_encrypt($data, 'aes-256-ctr', $this->kidDecoded, STREAM_CRYPTO_METHOD_TLS_SERVER, $iv);
$payload = json_encode([
'data' => '.' . base64_encode($cabecalho) . '.' . $this->ivBase64 . '.' . $hash128 . '.',
], JSON_UNESCAPED_SLASHES);
$this->uniqueIdMessage = self::generateUuid('');
$header = [
'Content-Type: application/json',
'accept-language: pt-BR',
'amc-aplicacao: PSD',
'amc-session-id: ' . $this->uniqueIdSession,
'amc-message-id: ' . $this->uniqueIdMessage
];
$options3 = [
CURLOPT_URL => $this->psd005,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => $header,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_USERAGENT => false,
CURLOPT_PORT => 443,
CURLOPT_CAINFO => realpath("cert/ca-safra.cer"),
CURLOPT_SSLCERT => realpath("cert/safr.combo.pem"),
CURLOPT_SSLKEY => realpath("cert/safr.key")
];
$curl = curl_init();
curl_setopt_array($curl, $options3);
$response = curl_exec($curl);
echo base64_decode($response);
$error = curl_error($curl);
if ($error) {
new Exception('Erro ao executar handshake 2 com safra');
}
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
echo 'http code <b>' . $httpCode . '</b> handshake 2 com safra <br>';
return json_decode($response);
}
//GERAR AUTHORIZATION
private function executeAuthorization($key)
{
$iv = base64_decode(explode('.', $key)[2]);
$dados = base64_decode(explode('.', $key)[3]);
//var_dump($dados);
$this->ivBase64 = base64_encode($iv);
$this->token = json_decode(openssl_decrypt($dados, 'aes-256-ctr', $this->kidDecoded, OPENSSL_CIPHER_AES_256_CBC, $iv));
$cabecalho = "{ alg: 'RSA-AEP', enc: 'A256CTR' }";
$data = [
'agencia' => '00000',
'conta' => '0000000',
'documento' => [
'nossonumero' => '8756025',
'seunumero' => '43243',
'diasdevolucao' => "30",
'especie' => '09',
'datavencimento' => '20191124',
'valor' => '510',
'codigomoeda' => '00',
'qtddiasprotesto' => '',
'identificacaoaceite' => 'N',
'desconto' => [
'data' => '20191117',
'valor' => '102',
],
'multa' => [
'juros' => '0.000333',
'data' => '',
'taxa' => '0.01',
],
'campolivre' => '',
'taxafidc' => '',
'danfe' => '',
'pagador' => [
'nome' => 'Teste',
'tipopessoa' => '1',
'numerodocumento' => '78556802257',
'email' => '',
'endereco' => [
'nome' => 'Rua Teste, 500',
'bairro' => 'Vila Nova',
'cidade' => 'SAO PAULO',
'uf' => 'SP',
'cep' => '00000000',
],
],
'beneficiario' => [
'nome' => 'NOME BENEFICIARIO',
'tipopessoa' => '2',
'numerodocumento' => '00000000000000',
'endereco' => [
'nome' => 'Rua Teste, 123',
'bairro' => 'Bela Vista',
'cidade' => 'Sao Paulo',
'uf' => 'SP',
'cep' => '00000000',
],
],
'mensagens' => [
0 => [
'posicao' => '1',
'descricao' => 'Multa após o vencimento: R$0,05.',
],
],
],
];
$hash128Data = openssl_encrypt(json_encode($data, JSON_FORCE_OBJECT), 'aes-256-ctr', $this->kidDecoded, STREAM_CRYPTO_METHOD_TLS_SERVER, $iv);
$body = json_encode([
'data' => '.' . base64_encode($cabecalho) . '.' . $this->ivBase64 . '.' . $hash128Data . '.',
], JSON_UNESCAPED_UNICODE);
$header = [
'Content-Type: application/json',
'accept-language: pt-BR',
'amc-aplicacao: PSD',
'amc-session-id: ' . $this->uniqueIdSession,
'amc-message-id: ' . self::generateUuid(''),
'amc-work-id: ' . self::generateUuid(''),
'authorization: Bearer ' . $this->token->token,
];
$options4 = array(
CURLOPT_URL => $this->psd050,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $header,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_USERAGENT => false,
CURLOPT_PORT => 443,
CURLOPT_SSLVERSION => 6,
CURLOPT_CAINFO => realpath("cert/ca-safra.cer"),
CURLOPT_CAPATH => realpath("cert/safr.combo.pem"),
CURLOPT_SSLKEY => realpath("cert/safr.key"),
);
$curl = curl_init();
curl_setopt_array($curl, $options4);
$response = curl_exec($curl);
$error = curl_error($curl);
if ($error) {
new Exception('Erro ao executar authorization com safra');
}
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
echo 'http code <b>' . $httpCode . '</b> gerar boleto com safra <br>';
return json_decode($response);
}
//GERAR BOLETO
public function executeGenerateBoleto($key)
{
$iv = base64_decode(explode('.', $key)[2]);
$dados = base64_decode(explode('.', $key)[3]);
$d = json_decode(openssl_decrypt($dados, 'aes-256-ctr', $this->kidDecoded, OPENSSL_CIPHER_AES_256_CBC, $iv));
$code = $d->statusProcessamento->code;
$message = $d->statusProcessamento->message;
if($code == 200)
{
$nossoNumero = $d->documento->nossonumero;
$seunumero = $d->documento->seunumero;
$codigobarras = $d->documento->codigobarras;
$codesucesso = $d->statusProcessamento->code;
$messagesucesso = $d->statusProcessamento->message;
echo '{"documento":{nossonumero":' . ' "' . $nossoNumero . '" ,' . '"seunumero":' . ' "' . $seunumero . '" ,' . '"codigobarras": "' . $codigobarras . '" ,' . '{"statusProcessamento":{"code": ' . ' "' . $codesucesso . '", ' . ' "message": ' . ' "' . $messagesucesso . '" }}' ;
}
else
{
echo '{"statusProcessamento":{"code": ' . ' "' . $code . '", ' . ' "message": ' . ' "' . $message . '" }}' ;
}
}
public function generateSignatureFromCertificate($certificate, $string)
{
$rsa = new Crypt_RSA();
$rsa->loadKey($certificate);
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->setHash('sha256');
$returnGenerate = $rsa->sign($string);
return $rsa->sign($string);
}
private static function generateUuid($node)
{
// nano second time (only micro second precision) since start of UTC
$time = microtime(true) * 10000000 + 0x01b21dd213814000;
$time = pack('H*', sprintf('%016x', $time));
$sequence = random_bytes(2);
$sequence[0] = \chr(\ord($sequence[0]) & 0x3f | 0x80); // variant bits 10x
$time[0] = \chr(\ord($time[0]) & 0x0f | 0x10); // version bits 0001
if (!empty($node)) {
// non hex string identifier
if (\is_string($node) && preg_match('/[^a-f0-9]/is', $node)) {
// base node off md5 hash for sequence
$node = md5($node);
// set multicast bit not IEEE 802 MAC
$node = (hexdec(substr($node, 0, 2)) | 1) . substr($node, 2, 10);
}
if (is_numeric($node)) {
$node = sprintf('%012x', $node);
}
if (\strlen($node) > 12) {
$node = substr($node, 0, 12);
}
} else {
// base node off random sequence
$node = random_bytes(6);
// set multicast bit not IEEE 802 MAC
$node[0] = \chr(\ord($node[0]) | 1);
$node = bin2hex($node);
}
$UUidReturn = bin2hex($time[4] . $time[5] . $time[6] . $time[7]) // time low
. '-' . bin2hex($time[2] . $time[3]) // time med
. '-' . bin2hex($time[0] . $time[1]) // time hi
. '-' . bin2hex($sequence) // seq
. '-' . $node;
// node
return $UUidReturn;
}
}
$safra = new Safra("H");
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment