├── .env.example ├── composer.json ├── contributors.txt ├── readme.md └── src ├── Customer.php ├── Http ├── Controllers │ ├── Controller.php │ └── PaypalController.php ├── Exceptions │ └── PaypalRequestException.php └── routes.php ├── Ipn.php ├── PaypalItem.php ├── Services ├── PaypalRequestService.php ├── PaypalSandboxRequestService.php └── PaypalServiceProvider.php ├── Transaction.php ├── config.php └── migrations ├── 2015_11_15_000013_create_paypal_ipns_table.php ├── 2015_11_15_000115_create_paypal_customers_table.php └── 2015_11_15_000137_create_paypal_transactions_table.php /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_DEBUG=true 3 | APP_KEY=SomeRandomString 4 | 5 | DB_HOST=localhost 6 | DB_DATABASE=homestead 7 | DB_USERNAME=homestead 8 | DB_PASSWORD=secret 9 | 10 | CACHE_DRIVER=file 11 | SESSION_DRIVER=file 12 | QUEUE_DRIVER=sync 13 | 14 | MAIL_DRIVER=smtp 15 | MAIL_HOST=mailtrap.io 16 | MAIL_PORT=2525 17 | MAIL_USERNAME=null 18 | MAIL_PASSWORD=null 19 | MAIL_ENCRYPTION=null 20 | MAIL_FROM_ADDRESS=null 21 | MAIL_FROM_NAME=null 22 | MAIL_TO=null 23 | 24 | PAYPAL_USERNAME=null 25 | PAYPAL_PASSWORD=null 26 | PAYPAL_SIGNATURE=null 27 | PAYPAL_VERSION=108.0 28 | PAYPAL_CURRENCYCODE=null 29 | PAYPAL_EMAIL=null 30 | PAYPAL_RETURNURL=http://paypal.app/pedido/ 31 | PAYPAL_NOTIFYURL=http://paypal.app/paypal/ipn 32 | PAYPAL_CANCELURL=http://paypal.app/pedido/cancel/ 33 | PAYPAL_BUTTONSOURCE=EMPRESA 34 | PAYPAL_HDRIMG=https://paypal.app/header-image.png 35 | 36 | PAYPAL_SANDBOX_USERNAME=null 37 | PAYPAL_SANDBOX_PASSWORD=null 38 | PAYPAL_SANDBOX_SIGNATURE=null 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resultsystems/laravel-paypal", 3 | "description": "Paypal Solution for Laravel 5.X", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Leandro Henrique Reis", 8 | "email": "emtudo@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.5.9", 13 | "illuminate/support": ">=5.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "ResultSystems\\Paypal\\": "src/" 18 | } 19 | }, 20 | "minimum-stability": "dev" 21 | } 22 | -------------------------------------------------------------------------------- /contributors.txt: -------------------------------------------------------------------------------- 1 | Leandro Henrique 2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Instalação: 2 | 3 | ``` 4 | composer require resultsystems/laravel-paypal 5 | ``` 6 | ou se preferir, adicione o seguinte trecho manualmente: 7 | 8 | ``` 9 | "require" : { 10 | "resultsystems/laravel-paypal": "~0.1" 11 | }, 12 | 13 | ``` 14 | 15 | ## Uso em sandbox 16 | 17 | Opcionalmente você pode criar em seu arquivo .env 18 | As seguintes variaveis: 19 | PAYPAL_SANDBOX_USERNAME=nome do usuário para sandbox 20 | PAYPAL_SANDBOX_PASSWORD=senha do usuário para sandbox 21 | PAYPAL_SANDBOX_SIGNATURE=assinatura para sandbox 22 | 23 | ### Class para uso em Sandbox 24 | 25 | Você pode apenas instanciar a classe `PaypalSandboxRequestService` em vez da `PaypalRequestService` para uso em sandbox 26 | 27 | 28 | ## Exemplos de uso 29 | 30 | ``` 31 | /** 32 | * Faz nova requisição 33 | * informando o número da fatura 15 34 | * usuario, senha e assinatura serão pegos das configurações 35 | */ 36 | $paypal = new \ResultSystems\Paypal\Services\PaypalRequestService(15); 37 | 38 | /*($quantity, $name, $description, $amount, $item_amount = null)*/ 39 | $paypal->addItem(new \ResultSystems\Paypal\PaypalItem('1.0', 'teste', 'xxx asdkflj daljkdfaslçkfj asdçlkfjsdalçfsd', '150')) 40 | ->setItem('1', 'teste 2', 'xxx 2', '39'); 41 | ``` 42 | 43 | ``` 44 | /*Vai para o checkout direto se passar*/ 45 | return $paypal->checkout(); 46 | ``` 47 | 48 | ``` 49 | /** 50 | * Executa a closure 51 | * após o processo de verificar os items 52 | * cabendo a closure redirecionar e fazer as demais coisas 53 | */ 54 | return $paypal->checkout(function ($response) { 55 | dd($response); 56 | }); 57 | 58 | return $paypal->getCheckoutUrl(function ($response) { 59 | dd($response); 60 | }); 61 | ``` 62 | 63 | ``` 64 | /** 65 | * Retorna a url para redirecionamento 66 | */ 67 | dd($paypal->getCheckoutUrl()); 68 | ``` 69 | 70 | ``` 71 | /** 72 | * Faz nova requisição 73 | * informando o número da fatura 15 74 | * passando usuario, senha e assintura 75 | */ 76 | $paypal = new \ResultSystems\Paypal\Services\PaypalRequestService(15, 'usuario', 'senha', 'assinatura'); 77 | ``` 78 | 79 | ``` 80 | /** 81 | * Faz nova requisição 82 | * informando o número da fatura 15 83 | * passando usuario e senha não informa a assinatura 84 | * pois vamos usar certificado 85 | */ 86 | $paypal = new \ResultSystems\Paypal\Services\PaypalRequestService(15, 'usuario', 'senha'); 87 | $paypla->setCertificateFile('certificado.txt'); 88 | ``` 89 | 90 | ``` 91 | /** 92 | * Faz nova requisição 93 | * informando o número da fatura 15 94 | * usuario, senha e assinatura serão pegos das configurações 95 | */ 96 | $paypal = new \ResultSystems\Paypal\Services\PaypalRequestService(15); 97 | 98 | /*($quantity, $name, $description, $amount, $item_amount = null)*/ 99 | $paypal->setSandbox()->addItem(new \ResultSystems\Paypal\PaypalItem('1.0', 'teste', 'xxx asdkflj daljkdfaslçkfj asdçlkfjsdalçfsd', '150')) 100 | ->addItem(new \ResultSystems\Paypal\PaypalItem('1', 'teste 2', 'xxx 2', '39')); 101 | 102 | /*Vai para o checkout direto*/ 103 | return $paypal->checkout(); 104 | ``` 105 | 106 | ``` 107 | /** 108 | * Faz nova requisição 109 | * informando o número da fatura 15 110 | * passando usuario, senha e assintura 111 | */ 112 | $paypal = new \ResultSystems\Paypal\Services\PaypalRequestService(15, 'usuario', 'senha', 'assinatura'); 113 | ``` 114 | 115 | ``` 116 | /** 117 | * Faz nova requisição 118 | * informando o número da fatura 15 119 | * passando usuario e senha não informa a assinatura 120 | * pois vamos usar certificado 121 | */ 122 | $paypal = new \ResultSystems\Paypal\Services\PaypalRequestService(15, 'usuario', 'senha'); 123 | $paypla->setCertificateFile('certificado.txt'); 124 | ``` 125 | 126 | ## Estorno total 127 | ``` 128 | $paypal = new \ResultSystems\Paypal\Services\PaypalRequestService; 129 | $transactionId=$_POST["transactionId"]; 130 | $note="Motivo de devolução do valor opcional aqui"; 131 | try 132 | { 133 | $id=$paypal->refundFull($transactionId, $note); 134 | echo 'transação estornada com sucesso!, id estorno: '.$id; 135 | } catch(\ResultSystems\Paypal\Exceptions\PaypalRequestException $e){ 136 | echo 'transação não foi estornada, houve uma falha: '.$e->getMessage(); 137 | } 138 | ``` 139 | 140 | ## Estorno parcial 141 | ``` 142 | $paypal = new \ResultSystems\Paypal\Services\PaypalRequestService; 143 | $transactionId=$_POST["transactionId"]; 144 | $amount=$_POST['amount']; 145 | $note="Motivo de devolução do valor opcional aqui"; 146 | try 147 | { 148 | $id=$paypal->refundPartial($transactionId, $note, $amount); 149 | echo 'transação estornada com sucesso!, id estorno: '.$id; 150 | } catch(\ResultSystems\Paypal\Exceptions\PaypalRequestException $e){ 151 | echo 'transação não foi estornada, houve uma falha: '.$e->getMessage(); 152 | } 153 | ``` 154 | 155 | ## Métodos públicos 156 | 157 | ``` 158 | /** 159 | * Constrói a classe de requisição de pagamento. 160 | * 161 | * @param int|string $invoice número da fatura 162 | * @param string $user usuário 163 | * @param string $pass senha 164 | * @param string $signature assinatura 165 | */ 166 | __construct($invoice = null, $user = null, $pass = null, $signature = null) 167 | 168 | 169 | /** 170 | * Adiciona itens. 171 | * 172 | * @param PaypalItem $item 173 | */ 174 | addItem(PaypalItem $item) 175 | 176 | /** 177 | * Cria um item e adiciona ao array de itens. 178 | * 179 | * @param float $quantity Quantidade de produtos 180 | * @param string $name Nome do produto 181 | * @param string $description Descrição do produto 182 | * @param float $amount Valor do produto 183 | */ 184 | setItem($quantity, $name, $description, $amount) 185 | 186 | /** 187 | * Pega a url para checkout 188 | * Se for passado uma closure 189 | * Executa ela informando o resultado para ela. 190 | * 191 | * @param closure $callback url para redirecionar caso falhe 192 | * 193 | * @return closure 194 | */ 195 | getCheckoutUrl($callback = null) 196 | 197 | /** 198 | * Vai para o checkout. 199 | * 200 | * @param function $callback [description] 201 | */ 202 | checkout($callback = null) 203 | 204 | /** 205 | * Seta o código da moeda. 206 | * 207 | * @param string $code ex: BRL, USD 208 | */ 209 | setCurrencyCode($code) 210 | 211 | /** 212 | * Seta se é sandbox ou produção 213 | * Caso o valor não seja boleano 214 | * não faz nada. 215 | * 216 | * @param bool $sandbox 217 | */ 218 | setSandbox($sandbox = true) 219 | 220 | /** 221 | * Seta o arquivo de certificado 222 | * Não é obrigatório. 223 | * 224 | * @param string $file 225 | */ 226 | setCertificateFile($file) 227 | 228 | /** 229 | * Pega dados da transação. 230 | * 231 | * @param string $token 232 | * 233 | * @return array 234 | */ 235 | getDatails($token) 236 | 237 | /** 238 | * Verifica se o pagamento está completo. 239 | * 240 | * @param string $token 241 | * 242 | * @return bool 243 | */ 244 | paymentCompleted($token) 245 | 246 | /** 247 | * STARTDATE Esse é o único campo obrigatório e especifica a data inicial. Qualquer transação cuja data for maior ou igual a especificada em STARTDATE será retornada pelo PayPal. 248 | * ENDDATE Ao contrário da STARTDATE, esse campo é opcional e especifica a data final. Qualquer transação que estiver entre a data inicial e a data final (inclusive) serão retornadas pela operação TransactionSearch. 249 | * EMAIL O campo email é utilizado para pesquisar transações de um comprador específico, se informado, apenas as transações daquele comprador serão retornadas. 250 | * RECEIVER Assim como o campo EMAIL, esse campo recebe um email, porém, o email do vendedor. Esse campo não é muito útil em lojas que operam com apenas 1 vendedor, mas em market places, que operam com vários vendedores, esse campo pode ser extremamente útil. 251 | * TRANSACTIONID Sempre que uma transação é criada no PayPal, um identificador de transação é retornado para a aplicação. Para pesquisar uma transação específica, podemos informar o id dessa transação nesse campo. 252 | * TRANSACTIONCLASS Existem diversas classes de transações e podemos pesquisar por transações que estejam em uma classe específica: 253 | * All – Vai retornar todas as transações, seria o mesmo que não enviar esse campo. 254 | * Sent – Somente transações de pagamentos enviados serão retornadas. 255 | * Received – Somente transações de pagamentos recebidos serão retornadas. 256 | * Refund – Somente transações envolvendo estornos. 257 | * AMT Esse campo permite uma pesquisa pelo valor da transação. 258 | * CURRENCYCODE Esse campo permite pesquisar as transações que foram feitas em uma determinada moeda (USD, BRL, etc.). 259 | * STATUS Permite uma pesquisa pelo status da transação: 260 | * Pending – Vai retornar apenas as transações pendente de revisão. 261 | * Processing – Vai retornar apenas as transações que estão em processamento. 262 | * Success – Vai retornar apenas as transações bem sucedidas, ou seja, aquelas que o pagamento foi concluído e o dinheiro transferido para o vendedor. 263 | * 264 | * transaction Search. 265 | * 266 | * @param array $data 267 | * 268 | * @return array 269 | */ 270 | transactionSearch(array $data) 271 | 272 | /** 273 | * Seta o número da fatura/pedido. 274 | * 275 | * @param int|string $id 276 | */ 277 | setInvoice($id) 278 | ``` 279 | -------------------------------------------------------------------------------- /src/Customer.php: -------------------------------------------------------------------------------- 1 | customer = $customer; 38 | $this->ipn = $ipn; 39 | $this->transaction = $transaction; 40 | } 41 | public function ipn(PaypalRequestService $paypal, Request $request) 42 | { 43 | $input = $request->all(); 44 | 45 | // dd($input); 46 | $data['data'] = json_encode($input); 47 | 48 | try { 49 | $r = Mail::send('emails.ipn', $data, function ($message) { 50 | $message 51 | ->replyTo('hlhenrique@gmail.com') 52 | ->to(env('MAIL_TO')) 53 | ->subject('Conctact: Paypal IPN!'); 54 | }); 55 | } catch (Exception $e) { 56 | } 57 | 58 | if (!$paypal->isIPNValid($input)) { 59 | return response()->json('Algo errado', 403); 60 | } 61 | 62 | //Está tudo correto, somos o destinatário da notificação, vamos 63 | //gravar um log dessa notificação. 64 | 65 | $ipn = new $this->ipn; 66 | $ipn->fill($input); 67 | try { 68 | $ipn->save(); 69 | } catch (Exception $e) { 70 | } 71 | 72 | //Log gravado, podemos seguir com as regras de negócio para 73 | //essa notificação. 74 | 75 | //gravamos dados do cliente 76 | $customer = new $this->customer; 77 | $customer->fill($input); 78 | try { 79 | if (!isset($input['email']) && isset($input['payer_email'])) { 80 | $input['email'] = $input['payer_email']; 81 | } 82 | $customer->save(); 83 | } catch (Exception $e) { 84 | } 85 | 86 | //gravamos dados da transação 87 | $transaction = new $this->transaction; 88 | $transaction->fill($input); 89 | try { 90 | $transaction->save(); 91 | } catch (Exception $e) { 92 | } 93 | 94 | return response()->json('Log travado'); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Http/Exceptions/PaypalRequestException.php: -------------------------------------------------------------------------------- 1 | name = $name; 32 | $this->description = $description; 33 | $this->amount = (double) $amount; 34 | $this->quantity = (double) $quantity; 35 | $this->item_amount = $amount; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Services/PaypalRequestService.php: -------------------------------------------------------------------------------- 1 | invoice = $invoice; 45 | $this->username = $user; 46 | $this->password = $pass; 47 | $this->signature = $signature; 48 | 49 | if (is_null($user)) { 50 | $this->username = Config::get('paypal.username', null); 51 | } 52 | 53 | if (is_null($pass)) { 54 | $this->password = Config::get('paypal.password', null); 55 | } 56 | 57 | if (is_null($signature)) { 58 | $this->signature = Config::get('paypal.signature', null); 59 | } 60 | } 61 | 62 | /** 63 | * Adiciona itens. 64 | * 65 | * @param PaypalItem $item 66 | */ 67 | public function addItem(PaypalItem $item) 68 | { 69 | $this->items[] = $item; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Cria um item e adiciona ao array de itens. 76 | * 77 | * @param float $quantity Quantidade de produtos 78 | * @param string $name Nome do produto 79 | * @param string $description Descrição do produto 80 | * @param float $amount Valor do produto 81 | */ 82 | public function setItem($quantity, $name, $description, $amount) 83 | { 84 | $this->addItem(new PaypalItem($quantity, $name, $description, $amount)); 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Pega o código da moeda. 91 | * 92 | * @return string 93 | */ 94 | protected function getCurrencyCode() 95 | { 96 | if (!is_null($this->currencyCode)) { 97 | return $this->currencyCode; 98 | } 99 | 100 | return Config::get('paypal.currencyCode', 'BRL'); 101 | } 102 | 103 | /** 104 | * Pega o total dos itens. 105 | * 106 | * @return float 107 | */ 108 | protected function getTotal() 109 | { 110 | if (count($this->items) < 1) { 111 | return 0; 112 | } 113 | 114 | $total = array_sum(array_map(function ($item) { 115 | if ($item->amount > 0) { 116 | return ($item->amount * $item->quantity); 117 | } 118 | 119 | return 0; 120 | }, $this->items)); 121 | 122 | if ($total > 0) { 123 | return $total; 124 | } 125 | throw new PaypalRequestException('Total is not valid'); 126 | } 127 | 128 | protected function getItems() 129 | { 130 | if (count($this->items) < 1) { 131 | throw new PaypalRequestException('No items'); 132 | } 133 | 134 | $items = []; 135 | foreach ($this->items as $key => $item) { 136 | $items['L_PAYMENTREQUEST_0_NAME' . $key] = $item->name; 137 | $items['L_PAYMENTREQUEST_0_DESC' . $key] = $item->description; 138 | $items['L_PAYMENTREQUEST_0_AMT' . $key] = $item->amount; 139 | $items['L_PAYMENTREQUEST_0_QTY' . $key] = $item->quantity; 140 | } 141 | // $items['L_PAYMENTREQUEST_0_ITEMAMT'] = $this->getTotal(); 142 | 143 | return $items; 144 | } 145 | 146 | /** 147 | * Pega a url de redirecionamento do paypal. 148 | * @return string 149 | */ 150 | protected function getPaypalUrl() 151 | { 152 | if ($this->sandbox) { 153 | //URL da PayPal para redirecionamento, não deve ser modificada 154 | return 'https://www.sandbox.paypal.com/cgi-bin/webscr'; 155 | } 156 | 157 | //URL da PayPal para redirecionamento, não deve ser modificada 158 | return 'https://www.paypal.com/cgi-bin/webscr'; 159 | } 160 | 161 | /** 162 | * Pega a url para checkout 163 | * Se for passado uma closure 164 | * Executa ela informando o resultado para ela. 165 | * 166 | * @param closure $callback url para redirecionar caso falhe 167 | * 168 | * @return closure 169 | */ 170 | public function getCheckoutUrl($callback = null) 171 | { 172 | $requestNvp = $this->getCredentials(); 173 | $requestNvp = array_merge($requestNvp, [ 174 | 'METHOD' => 'SetExpressCheckout', 175 | 'PAYMENTREQUEST_0_PAYMENTACTION' => 'SALE', 176 | 'PAYMENTREQUEST_0_AMT' => (string) $this->getTotal(), 177 | 'PAYMENTREQUEST_0_CURRENCYCODE' => $this->getCurrencyCode(), 178 | 'PAYMENTREQUEST_0_ITEMAMT' => (string) $this->getTotal(), 179 | 'PAYMENTREQUEST_0_INVNUM' => (string) $this->invoice, 180 | 181 | 'RETURNURL' => Config::get('paypal.returnurl', ''), 182 | 'CANCELURL' => Config::get('paypal.cancelurl', ''), 183 | // 'BUTTONSOURCE' => Config::get('paypal.buttonsource', ''), 184 | ], $this->getItems()); 185 | 186 | //Envia a requisição e obtém a resposta da PayPal 187 | $responseNvp = $this->sendNvpRequest($requestNvp, $this->sandbox); 188 | if (is_callable($callback)) { 189 | return $callback($responseNvp); 190 | }; 191 | 192 | if (!isset($responseNvp['TOKEN'])) { 193 | throw new PaypalRequestException("Token don't exists."); 194 | } 195 | 196 | $query = [ 197 | 'cmd' => '_express-checkout', 198 | 'token' => $responseNvp['TOKEN'], 199 | ]; 200 | 201 | $redirectURL = sprintf('%s?%s', $this->getPaypalUrl(), http_build_query($query)); 202 | 203 | return $redirectURL; 204 | } 205 | 206 | /** 207 | * Vai para o checkout. 208 | * 209 | * @param closure $callback 210 | */ 211 | public function checkout($callback = null) 212 | { 213 | return $this->getCheckoutUrl(function ($response) use ($callback) { 214 | if (is_callable($callback)) { 215 | return $callback($response); 216 | }; 217 | 218 | $query = [ 219 | 'cmd' => '_express-checkout', 220 | 'token' => $response['TOKEN'], 221 | ]; 222 | 223 | $redirectURL = sprintf('%s?%s', $this->getPaypalUrl(), http_build_query($query)); 224 | 225 | return redirect($redirectURL); 226 | }); 227 | } 228 | 229 | /** 230 | * Seta o código da moeda. 231 | * 232 | * @param string $code ex: BRL, USD 233 | */ 234 | public function setCurrencyCode($code) 235 | { 236 | $this->currencyCode = $code; 237 | 238 | return $this; 239 | } 240 | 241 | /** 242 | * Seta se é sandbox ou produção 243 | * Caso o valor não seja boleano 244 | * não faz nada. 245 | * 246 | * @param bool $sandbox 247 | */ 248 | public function setSandbox($sandbox = true) 249 | { 250 | if (is_bool($sandbox)) { 251 | $this->sandbox = $sandbox; 252 | } 253 | 254 | return $this; 255 | } 256 | 257 | /** 258 | * Envia uma requisição NVP para uma API PayPal. 259 | * 260 | * @param array $requestNvp Define os campos da requisição. 261 | * @param bool $sandbox Define se a requisição será feita no sandbox ou no 262 | * ambiente de produção. 263 | * 264 | * @return array Campos retornados pela operação da API. O array de retorno poderá 265 | * ser vazio, caso a operação não seja bem sucedida. Nesse caso, os 266 | * logs de erro deverão ser verificados. 267 | */ 268 | protected function sendNvpRequest(array $requestNvp, $sandbox = false) 269 | { 270 | //Endpoint da API 271 | $apiEndpoint = 'https://api-3t.' . ($sandbox ? 'sandbox.' : null); 272 | $apiEndpoint .= 'paypal.com/nvp'; 273 | //Executando a operação 274 | $curl = curl_init(); 275 | 276 | curl_setopt($curl, CURLOPT_URL, $apiEndpoint); 277 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->certificate); 278 | 279 | if ($this->certificate) { 280 | curl_setopt($curl, CURLOPT_SSLCERT, $this->certificateFile); 281 | } 282 | 283 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 284 | curl_setopt($curl, CURLOPT_POST, true); 285 | curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($requestNvp)); 286 | 287 | $response = urldecode(curl_exec($curl)); 288 | 289 | curl_close($curl); 290 | //Tratando a resposta 291 | $responseNvp = []; 292 | 293 | if (preg_match_all('/(?[^\=]+)\=(?[^&]+)&?/', $response, $matches)) { 294 | foreach ($matches['name'] as $offset => $name) { 295 | $responseNvp[$name] = $matches['value'][$offset]; 296 | } 297 | } 298 | 299 | //Verificando se deu tudo certo e, caso algum erro tenha ocorrido, 300 | //gravamos um log para depuração. 301 | 302 | if (isset($responseNvp['ACK']) && $responseNvp['ACK'] != 'Success') { 303 | $errors = []; 304 | for ($i = 0;isset($responseNvp['L_ERRORCODE' . $i]); ++$i) { 305 | $message = sprintf("PayPal NVP %s[%d]: %s\n", 306 | $responseNvp['L_SEVERITYCODE' . $i], 307 | $responseNvp['L_ERRORCODE' . $i], 308 | $responseNvp['L_LONGMESSAGE' . $i]); 309 | 310 | $errors[] = $message; 311 | } 312 | 313 | throw new PaypalRequestException(implode(',', $errors)); 314 | } 315 | 316 | return $responseNvp; 317 | } 318 | 319 | /** 320 | * Seta o arquivo de certificado 321 | * Não é obrigatório. 322 | * 323 | * @param string $file 324 | */ 325 | public function setCertificateFile($file) 326 | { 327 | $this->certificate = true; 328 | $this->certificateFile = $file; 329 | 330 | return $this; 331 | } 332 | 333 | /** 334 | * Pega as credencias. 335 | * 336 | * @return array 337 | */ 338 | protected function getCredentials() 339 | { 340 | //credenciais da API para o Sandbox 341 | // $usernameSandbox = 'conta-business_api1.test.com'; 342 | // $passwordSandbox = '1365001380'; 343 | // $signatureSandbox = 'AiPC9BjkCyDFQXbSkoZcgqH3hpacA-p.YLGfQjc0EobtODs.fMJNajCx'; 344 | $user = $this->username; 345 | $pswd = $this->password; 346 | $signature = $this->signature; 347 | 348 | if ($this->sandbox) { 349 | $user = env('PAYPAL_SANDBOX_USERNAME', $user); 350 | $pswd = env('PAYPAL_SANDBOX_PASSWORD', $pswd); 351 | $signature = env('PAYPAL_SANDBOX_SIGNATURE', $signature); 352 | } 353 | 354 | if (is_null($user) || is_null($pswd)) { 355 | throw new PaypalRequestException('No credentials'); 356 | } 357 | 358 | if (is_null($signature) && !$this->certificate) { 359 | throw new PaypalRequestException('No credentials'); 360 | } 361 | 362 | if (is_null($user) || is_null($pswd)) { 363 | throw new PaypalRequestException('No credentials'); 364 | } 365 | 366 | if (is_null($signature) && !$this->certificate) { 367 | throw new PaypalRequestException('No credentials'); 368 | } 369 | 370 | $requestNvp = [ 371 | 'USER' => $user, 372 | 'PWD' => $pswd, 373 | 374 | 'VERSION' => Config::get('paypal.version', '108.0'), 375 | 'LOCALECODE' => $this->localeCode, 376 | ]; 377 | 378 | $hdimg = Config::get('paypal.HDRIMG', false); 379 | if ($hdimg) { 380 | $requestNvp['HDRIMG'] = $hdimg; 381 | } 382 | 383 | if (!$this->certificate) { 384 | $requestNvp = array_merge(['SIGNATURE' => $signature], $requestNvp); 385 | } 386 | 387 | return $requestNvp; 388 | } 389 | 390 | /** 391 | * Pega dados da transação. 392 | * 393 | * @param string $token 394 | * 395 | * @return array 396 | */ 397 | public function getDatails($token) 398 | { 399 | $requestNvp = $this->getCredentials(); 400 | $requestNvp = array_merge($requestNvp, [ 401 | 'METHOD' => 'GetExpressCheckoutDetails', 402 | 'TOKEN' => $token, 403 | ]); 404 | 405 | return $this->sendNvpRequest($requestNvp, $this->sandbox); 406 | } 407 | 408 | /** 409 | * Faz o do ExpresseChecoutPayment. 410 | * 411 | * @param string $token 412 | * 413 | * @return array 414 | */ 415 | public function doExpressCheckoutPayment($token) 416 | { 417 | $requestNvp = $this->getCredentials(); 418 | $requestNvp = array_merge($requestNvp, $this->getDatails($token), [ 419 | 'METHOD' => 'DoExpressCheckoutPayment', 420 | 'NOTIFYURL' => Config::get('paypal.notifyurl', 'http://paypal.app/paypal/ipn'), 421 | 'TOKEN' => $token, 422 | ]); 423 | 424 | return $this->sendNvpRequest($requestNvp, $this->sandbox); 425 | } 426 | 427 | /** 428 | * Verifica se o pagamento está completo. 429 | * 430 | * @param string $token 431 | * 432 | * @return bool 433 | */ 434 | public function paymentCompleted($token) 435 | { 436 | $dtails = $this->getDatails($token); 437 | if (!isset($details['CHECKOUTSTATUS'])) { 438 | return false; 439 | } 440 | 441 | return ($details['CHECKOUTSTATUS'] == 'PaymentCompleted'); 442 | } 443 | 444 | /** 445 | * STARTDATE Esse é o único campo obrigatório e especifica a data inicial. Qualquer transação cuja data for maior ou igual a especificada em STARTDATE será retornada pelo PayPal. 446 | * ENDDATE Ao contrário da STARTDATE, esse campo é opcional e especifica a data final. Qualquer transação que estiver entre a data inicial e a data final (inclusive) serão retornadas pela operação TransactionSearch. 447 | * EMAIL O campo email é utilizado para pesquisar transações de um comprador específico, se informado, apenas as transações daquele comprador serão retornadas. 448 | * RECEIVER Assim como o campo EMAIL, esse campo recebe um email, porém, o email do vendedor. Esse campo não é muito útil em lojas que operam com apenas 1 vendedor, mas em market places, que operam com vários vendedores, esse campo pode ser extremamente útil. 449 | * TRANSACTIONID Sempre que uma transação é criada no PayPal, um identificador de transação é retornado para a aplicação. Para pesquisar uma transação específica, podemos informar o id dessa transação nesse campo. 450 | * TRANSACTIONCLASS Existem diversas classes de transações e podemos pesquisar por transações que estejam em uma classe específica: 451 | * All – Vai retornar todas as transações, seria o mesmo que não enviar esse campo. 452 | * Sent – Somente transações de pagamentos enviados serão retornadas. 453 | * Received – Somente transações de pagamentos recebidos serão retornadas. 454 | * Refund – Somente transações envolvendo estornos. 455 | * AMT Esse campo permite uma pesquisa pelo valor da transação. 456 | * CURRENCYCODE Esse campo permite pesquisar as transações que foram feitas em uma determinada moeda (USD, BRL, etc.). 457 | * STATUS Permite uma pesquisa pelo status da transação: 458 | * Pending – Vai retornar apenas as transações pendente de revisão. 459 | * Processing – Vai retornar apenas as transações que estão em processamento. 460 | * Success – Vai retornar apenas as transações bem sucedidas, ou seja, aquelas que o pagamento foi concluído e o dinheiro transferido para o vendedor. 461 | * 462 | * transaction Search. 463 | * 464 | * @param array $data 465 | * 466 | * @return array 467 | */ 468 | public function transactionSearch(array $data) 469 | { 470 | if (!isset($data['STARTDATE'])) { 471 | $data['STARTDATE'] = '2014-02-03T00:00:00Z'; 472 | } 473 | 474 | $requestNvp = $this->getCredentials(); 475 | 476 | //Campos da requisição da operação TransactionSearch, como ilustrado acima. 477 | $requestNvp = array_merge($requestNvp, ['METHOD' => 'TransactionSearch'], $data); 478 | 479 | //Envia a requisição e obtém a resposta da PayPal 480 | $responseNvp = $this->sendNvpRequest($requestNvp, $this->sandbox); 481 | 482 | //Se a operação tiver sido bem sucedida, podemos listar as transações encontradas 483 | if (isset($responseNvp['ACK']) && $responseNvp['ACK'] == 'Success') { 484 | return $responseNvp; 485 | } 486 | 487 | throw new PaypalRequestException('No results'); 488 | } 489 | 490 | /** 491 | * Seta o número da fatura/pedido. 492 | * 493 | * @param int|string $id 494 | */ 495 | public function setInvoice($id) 496 | { 497 | $this->invoice = $id; 498 | 499 | return $this; 500 | } 501 | 502 | /** 503 | * Verifica se uma notificação IPN é válida, fazendo a autenticação 504 | * da mensagem segundo o protocolo de segurança do serviço. 505 | * 506 | * @param array $data Um array contendo a notificação recebida. 507 | * 508 | * @return bool TRUE se a notificação for autência, ou FALSE se 509 | * não for. 510 | */ 511 | public function isIPNValid(array $data) 512 | { 513 | $endpoint = 'https://www.paypal.com'; 514 | 515 | if (isset($data['test_ipn']) && $data['test_ipn'] == '1') { 516 | $endpoint = 'https://www.sandbox.paypal.com'; 517 | } 518 | 519 | $endpoint .= '/cgi-bin/webscr?cmd=_notify-validate'; 520 | 521 | $curl = curl_init(); 522 | 523 | curl_setopt($curl, CURLOPT_URL, $endpoint); 524 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 525 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 526 | curl_setopt($curl, CURLOPT_POST, true); 527 | curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); 528 | 529 | $response = curl_exec($curl); 530 | $error = curl_error($curl); 531 | $errno = curl_errno($curl); 532 | 533 | curl_close($curl); 534 | 535 | $valid = empty($error) && $errno == 0 && $response == 'VERIFIED'; 536 | 537 | if (!$valid) { 538 | return false; 539 | } 540 | 541 | //Se chegamos até aqui, significa que estamos lidando com uma 542 | //notificação IPN válida. Agora precisamos verificar se somos o 543 | //destinatário dessa notificação, verificando o campo receiver_email. 544 | return ($data['receiver_email'] == Config::get('paypal.email')); 545 | } 546 | 547 | /** 548 | * Define a linguagem local. 549 | * 550 | * @param string $code 551 | */ 552 | public function setLocaleCode($code = 'pt_BR') 553 | { 554 | $this->localeCode = $code; 555 | 556 | return $this; 557 | } 558 | 559 | /** 560 | * Estorna a transação, parcial ou completa. 561 | * 562 | * @param string $transactionId 563 | * @param float $amount 564 | * 565 | * @return array 566 | */ 567 | public function refundTransaction($transactionId, $note = null, $amount = null) 568 | { 569 | $requestNvp = $this->getCredentials(); 570 | $type = ['REFUNDTYPE' => 'Full']; 571 | if (!is_null($amount)) { 572 | $type = [ 573 | 'REFUNDTYPE' => 'Partial', 574 | 'AMT' => str_replace(',', '.', str_replace('.', '', $amount)), 575 | 'CURRENCYCODE' => $this->getCurrencyCode()]; 576 | } 577 | 578 | $requestNvp = array_merge($requestNvp, [ 579 | 'METHOD' => 'RefundTransaction', 580 | 581 | 'TRANSACTIONID' => $transactionId], $type); 582 | 583 | //Envia a requisição e obtém a resposta da PayPal 584 | if (!is_null($note)) { 585 | $requestNvp['NOTE'] = $note; 586 | } 587 | $responseNvp = $this->sendNvpRequest($requestNvp, $this->sandbox); 588 | 589 | // echo '

A transação '.$transactionId.' foi estonada com sucesso. O transactionId do estorno é: '.$responseNvp['REFUNDTRANSACTIONID'].'

'; 590 | 591 | return $responseNvp; 592 | } 593 | 594 | /** 595 | * Estorna a transação por completa. 596 | * 597 | * @param string $transactionId 598 | * 599 | * @return bool|string id da transação de estorno 600 | */ 601 | public function refundFull($transactionId, $note = null) 602 | { 603 | $responseNvp = $this->refundTransaction($transactionId, $note); 604 | //Verifica se a operação foi bem sucedida 605 | if (isset($responseNvp['ACK']) && $responseNvp['ACK'] == 'Success') { 606 | return $responseNvp['REFUNDTRANSACTIONID']; 607 | } 608 | 609 | return false; 610 | } 611 | 612 | /** 613 | * Estorna um valor parcial da transação. 614 | * 615 | * @param string $transactionId 616 | * @param float $amount 617 | * 618 | * @return bool|string id da transação de estorno 619 | */ 620 | public function refundPartial($transactionId, $amount, $note = null) 621 | { 622 | $responseNvp = $this->refundTransaction($transactionId, $note, $amount); 623 | //Verifica se a operação foi bem sucedida 624 | if (isset($responseNvp['ACK']) && $responseNvp['ACK'] == 'Success') { 625 | return $responseNvp['REFUNDTRANSACTIONID']; 626 | } 627 | 628 | return false; 629 | } 630 | } 631 | -------------------------------------------------------------------------------- /src/Services/PaypalSandboxRequestService.php: -------------------------------------------------------------------------------- 1 | registerRoutes(); 22 | } 23 | 24 | public function register() 25 | { 26 | $this->registerConfig(); 27 | $this->publishMigrations(); 28 | } 29 | 30 | /** 31 | * Register config. 32 | * 33 | * @return void 34 | */ 35 | protected function registerConfig() 36 | { 37 | $this->publishes([ 38 | __DIR__.'/../config.php' => config_path('paypal.php'), 39 | ]); 40 | 41 | $this->mergeConfigFrom( 42 | __DIR__.'/../config.php', 'paypal' 43 | ); 44 | } 45 | 46 | protected function registerRoutes() 47 | { 48 | require __DIR__.'/../Http/routes.php'; 49 | } 50 | 51 | /** 52 | * Publish migration file. 53 | */ 54 | private function publishMigrations() 55 | { 56 | $this->publishes([__DIR__.'/../migrations/' => base_path('database/migrations')], 'migrations'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Transaction.php: -------------------------------------------------------------------------------- 1 | env('PAYPAL_USERNAME', ''), 6 | 'password' => env('PAYPAL_PASSWORD', ''), 7 | 'signature' => env('PAYPAL_SIGNATURE', ''), 8 | 9 | /* Email usando apenas para transação IPN */ 10 | 'email' => env('PAYPAL_EMAIL', ''), 11 | 12 | /* Moeda a trabalhar */ 13 | 'currencyCode' => env('PAYPAL_CURRENCYCODE', 'BRL'), 14 | 15 | /* Linguagem de exibição */ 16 | 'locadeCode' => env('PAYPAL_LOCALECODE', 'pt_BR'), 17 | 18 | /* Logo */ 19 | /* A URL do cabeçalho da página deve ter 750px de largura por 90px de altura e deve estar armazenada em um servidor seguro. */ 20 | 'HDRIMG' => env('PAYPAL_HDRIMG', false), //Obrigatório https 21 | 22 | //'HDRIMG' => 'https://paypal.app/header-image.png', //Obrigatório https 23 | 24 | /* URLs de retorno */ 25 | 'notifynurl' => env('PAYPAL_NOTIFYURL', 'http://paypal.app/paypal/ipn/'), 26 | 'returnurl' => env('PAYPAL_RETURNURL', 'http://paypal.app/pedido/'), 27 | 'cancelurl' => env('PAYPAL_CANCELURL', 'http://paypal.app/pedido/cancel/'), 28 | 'buttonsource' => env('PAYPAL_BUTTONSOURCE', 'EMPRESA'), 29 | 30 | /* Prefixo da rota para log de IPN */ 31 | 'route_prefix' => '/paypal/', 32 | 33 | /* Versão da API do Paypal a utilizar */ 34 | 'version' => env('PAYPAL_VERSION', '108.0'), 35 | ]; 36 | -------------------------------------------------------------------------------- /src/migrations/2015_11_15_000013_create_paypal_ipns_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 17 | $table->string('txn_id'); 18 | $table->string('txn_type', 45)->nullable()->default(null); 19 | $table->string('receiver_email', 127); 20 | $table->string('payment_status', 17)->nullable()->default(null); 21 | $table->string('pending_reason', 17)->nullable()->default(null); 22 | $table->string('reason_code', 31)->nullable()->default(null); 23 | $table->string('custom', 45)->nullable()->default(null); 24 | $table->string('invoice', 45)->nullable()->default(null); 25 | $table->text('notification'); 26 | $table->string('hash', 32)->unique(); 27 | $table->timestamps(); 28 | 29 | $table->index(['custom', 'payment_status']); 30 | $table->index(['invoice', 'payment_status']); 31 | $table->index(['txn_type', 'payment_status']); 32 | $table->index(['txn_id', 'payment_status']); 33 | }); 34 | } 35 | 36 | /** 37 | * Reverse the migrations. 38 | * 39 | * @return void 40 | */ 41 | public function down() 42 | { 43 | Schema::dropIfExists('paypal_ipns'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/migrations/2015_11_15_000115_create_paypal_customers_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 17 | $table->string('address_country', 64)->nullable()->default(null); 18 | $table->string('address_city', 40)->nullable()->default(null); 19 | $table->string('address_country_code', 2)->nullable()->default(null); 20 | $table->string('address_name', 128)->nullable()->default(null); 21 | $table->string('address_state', 40)->nullable()->default(null); 22 | $table->string('address_status', 11)->nullable()->default(null); 23 | $table->string('address_street', 200)->nullable()->default(null); 24 | $table->string('address_zip', 20)->nullable()->default(null); 25 | $table->string('contact_phone', 20)->nullable()->default(null); 26 | $table->string('first_name', 64)->nullable()->default(null); 27 | $table->string('last_name', 64)->nullable()->default(null); 28 | $table->string('business_name', 127)->nullable()->default(null); 29 | $table->string('email', 127)->nullable()->unique(); 30 | $table->string('paypal_id', 13)->unique(); 31 | $table->timestamps(); 32 | }); 33 | } 34 | 35 | /** 36 | * Reverse the migrations. 37 | * 38 | * @return void 39 | */ 40 | public function down() 41 | { 42 | Schema::dropIfExists('paypal_customers'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/migrations/2015_11_15_000137_create_paypal_transactions_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 17 | $table->string('invoice', 127)->nullable(); 18 | $table->string('custom', 255)->nullable(); 19 | $table->string('txn_type', 55); 20 | $table->string('txn_id'); 21 | $table->string('payer_id', 13); 22 | $table->string('currency', 3); 23 | $table->double('gross'); 24 | $table->double('fee'); 25 | $table->double('handling')->nullable(); 26 | $table->double('shipping')->nullable(); 27 | $table->double('tax')->nullable(); 28 | $table->string('payment_status', 17)->nullable(); 29 | $table->string('pending_reason', 17)->nullable(); 30 | $table->string('reason_code', 31)->nullable(); 31 | $table->timestamps(); 32 | 33 | //$table->index(['payer_id', ' payment_status']); 34 | //$table->index(['txn_id', ' payment_status']); 35 | //$table->index(['custom', ' payment_status']); 36 | //$table->index(['invoice', ' payment_status']); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | * 43 | * @return void 44 | */ 45 | public function down() 46 | { 47 | Schema::dropIfExists('paypal_transactions'); 48 | } 49 | } 50 | --------------------------------------------------------------------------------