Pagopar.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <?php
  2. /**
  3. * Archivo SDK de Pagopar
  4. * @author "Pagopar" <desarrollo@pagopar.com>
  5. * @version 1 27/4/2017
  6. */
  7. require_once 'lib/DBPagopar.php';
  8. require_once 'classes/OrderPagopar.php';
  9. require_once 'classes/ConsultPagopar.php';
  10. class Pagopar{
  11. //URLs de configuración
  12. const URL_BASE = 'https://api.pagopar.com/api/';
  13. const URL_COMERCIOS = self::URL_BASE.'comercios/1.1/iniciar-transaccion';
  14. const URL_PEDIDOS = self::URL_BASE.'pedidos/1.1/traer';
  15. const URL_FLETE = self::URL_BASE.'calcular-flete/1.1/traer';
  16. const URL_CATEGORIAS = self::URL_BASE.'categorias/1.1/traer';
  17. const URL_CIUDADES = self::URL_BASE.'ciudades/1.1/traer';
  18. const URL_REDIRECT = 'https://pagopar.com/pagos/%s';
  19. //Tipos de Tokens generados
  20. const TOKEN_TIPO_CONSULTA = 'CONSULTA';
  21. const TOKEN_TIPO_CIUDAD = 'CIUDADES';
  22. const TOKEN_TIPO_CATEGORIA = 'CATEGORIAS';
  23. const TOKEN_TIPO_FLETE = 'CALCULAR-FLETE';
  24. //Base de datos
  25. protected $db;
  26. //Datos del pedido del comercio
  27. private $idOrder;
  28. private $hashOrder;
  29. public $order;
  30. private $preOrder;
  31. private $itemsIdMethods;
  32. public $privateKey = null;
  33. public $publicKey = null;
  34. /**
  35. * Constructor de la clase
  36. * @param int $id Id del pedido
  37. * @param $db
  38. * @internal param Database $PDO $db Base de Datos (Basada en PDO)
  39. */
  40. public function __construct($id = null,$db) {
  41. $this->db = $db;
  42. $this->idOrder = $id;
  43. $this->order = new OrderPagopar($id);
  44. }
  45. /**
  46. * Invoca a la URL de acuerdo a los parámetros
  47. * @param array $args Parámetros
  48. * @param string $url Url a invocar
  49. * @return string Respuesta en formato JSON
  50. */
  51. private function runCurl($args, $url){
  52. $args = json_encode($args);
  53. $ch = curl_init();
  54. $headers= array('Accept: application/json','Content-Type: application/json');
  55. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  56. curl_setopt($ch, CURLOPT_URL, $url);
  57. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  58. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  59. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
  60. curl_setopt($ch, CURLOPT_POST, true);
  61. curl_setopt($ch, CURLOPT_POSTFIELDS, $args);
  62. $response = curl_exec($ch);
  63. $error = curl_error($ch);
  64. $info = curl_getinfo($ch);
  65. curl_close($ch);
  66. return $response;
  67. }
  68. /**
  69. * Guarda en un array ($itemsIdMethods) los id_producto de los ítems y sus índices en el array
  70. * compras_items ([id_producto => índice]) para que tengamos un acceso más rápido a los ítems.
  71. * Además, para los ítems con más de un método de envío, guardamos los tipos de envío disponibles
  72. * en un array ($multiple_methods) con sus valores para retornar después al usuario.
  73. * @param array $arrayResponse Array que contiene las distintas opciones de envío de los ítems.
  74. * @return string Respuesta en formato JSON que contiene las distintas opciones de envío de los ítems.
  75. * Si se retorna false, se asume que no existen opciones de envío o que sólo existe una opción
  76. * de envío para cada ítem.
  77. */
  78. private function saveMethodsOfShipping($arrayResponse){
  79. $multiple_methods = [];
  80. $this->itemsIdMethods = [];
  81. //Si alguno de los ítems devueltos tiene los dos métodos de pago,
  82. foreach ($arrayResponse->compras_items as $index => $item) {
  83. //En un array guardamos los ids de los productos como índices y los índices como valor
  84. //para que sean más fáciles de encontrar después.
  85. $this->itemsIdMethods[$item->id_producto] = $index;
  86. if(isset($item->opciones_envio) && isset($item->opciones_envio->metodo_aex) && isset($item->opciones_envio->propio)){
  87. $multiple_methods[$item->id_producto] = [
  88. "metodo_aex"=>$item->opciones_envio['metodo_aex'],
  89. "propio"=>$item->opciones_envio['propio']
  90. ];
  91. }
  92. }
  93. //Guardamos la respuesta en preOrder
  94. $this->preOrder = $arrayResponse;
  95. return (!empty($multiple_methods))?json_encode($multiple_methods):false;
  96. }
  97. /**
  98. * Inicia la conexión con Pagopar y obtiene los métodos de envío para cada ítem
  99. * @return string|boolean Respuesta en formato JSON que contiene las distintas opciones de envío de los ítems.
  100. * Si se retorna false, se asume que no existen opciones de envío o que sólo existe una opción
  101. * de envío para cada ítem.
  102. */
  103. public function getMethodsOfShipping(){
  104. $orderPagopar = $this->order->makeOrder();
  105. $token = $this->generateToken(self::TOKEN_TIPO_FLETE);
  106. $args = ['token'=>$token,'token_publico'=>$this->order->publicKey, 'dato'=>json_encode($orderPagopar)];
  107. $response = $this->runCurl($args, self::URL_FLETE);
  108. $arrayResponse = json_decode($response);
  109. if(isset($arrayResponse->respuesta)&&!$arrayResponse->respuesta){
  110. throw new Exception($arrayResponse->resultado);
  111. }elseif(isset($arrayResponse->resultado) && $arrayResponse->resultado === "Sin datos"){
  112. throw new Exception($arrayResponse->resultado);
  113. }
  114. return $this->saveMethodsOfShipping($arrayResponse);
  115. }
  116. /**
  117. * Valida el JSON de los métodos de envío seleccionados.
  118. * @param array $items Array con los métodos de envío seleccionados. Tiene esta estructura:
  119. * ["id_producto_1" => "metodo_1", "id_producto_2" => "metodo_2", ... => ...]
  120. * @return bool True si el JSON es válido (los id de los productos existen y los métodos de pago existen),
  121. * False si no es un JSON válido.
  122. */
  123. private function validateItemsMethodsJSON($items){
  124. foreach ($items as $id => $method){
  125. if(array_key_exists($id,$this->itemsIdMethods)){
  126. if(array_key_exists($method,$this->preOrder->compras_items[$this->itemsIdMethods[$id]]->opciones_envio)){
  127. return true;
  128. }else{
  129. return false;
  130. }
  131. }else{
  132. return false;
  133. }
  134. }
  135. return false;
  136. }
  137. /**
  138. * Completa los ítems de la orden con el campo envio_seleccionado
  139. * @param string $json_items Cadena JSON que contiene las opciones de envío de los ítems.
  140. * @return array Array de respuesta que contiene la Orden con el flete calculado de los ítems o
  141. * un array de error en caso de fallo.
  142. * @throws Exception
  143. */
  144. private function fillShippingOptions($json_items){
  145. $items_methods = null;
  146. if($json_items) {
  147. $items_methods = json_decode($json_items);
  148. if (!$this->validateItemsMethodsJSON($items_methods)) {
  149. throw new Exception("Hay un error con el JSON de los métodos seleccionados de los items");
  150. }
  151. }
  152. foreach($this->preOrder->compras_items as $index => $item){
  153. //Obtenemos el primer y único elemento en el caso de que sólo haya un método seleccionado
  154. if(isset($item->opciones_envio)){
  155. $key = key($item->opciones_envio);
  156. $item->envio_seleccionado = ($key==="metodo_aex")?"aex":$key;
  157. }
  158. }
  159. //Completamos los campos necesarios para envio_seleccionado
  160. if($json_items){
  161. foreach($items_methods as $id => $method){
  162. $this->preOrder->compras_items[$this->itemsIdMethods[$id]]->envio_seleccionado = $method;
  163. }
  164. }
  165. //Calculamos el flete
  166. return $this->getShippingCost();
  167. }
  168. /**
  169. * Calcula el flete de los ítems si es necesario
  170. * @return array Array de respuesta que contiene la Orden con el flete calculado de los ítems o
  171. * un array de error en caso de fallo.
  172. */
  173. private function getShippingCost(){
  174. $token = $this->generateToken(self::TOKEN_TIPO_FLETE);
  175. $args = ['token'=>$token,'token_publico'=>$this->order->publicKey, 'dato'=> json_encode($this->preOrder)];
  176. $response = $this->runCurl($args, self::URL_FLETE);
  177. return json_decode($response);
  178. }
  179. /**
  180. * Reenvía el pedido a Pagopar (con el flete calculado) y genera una transacción. Si tiene éxito al
  181. * generar el pedido, redirecciona a la página de pago de Pagopar.
  182. * @param string $json_items Cadena JSON que contiene las opciones de envío de los ítems.
  183. * Si no recibe este parámetro, se asume que no existen opciones de envío o que sólo existe una opción
  184. * de envío para cada ítem.
  185. * @throws Exception
  186. */
  187. public function newPagoparTransaction($json_items=null){
  188. $response = $this->fillShippingOptions($json_items);
  189. //Verificar si hay error
  190. if(isset($response->respuesta) && !$response->respuesta){
  191. throw new Exception($response->resultado);
  192. }
  193. //Asignamos de nuevo a la orden la respuesta
  194. $this->preOrder = $response;
  195. //Generamos de nuevo el token
  196. $this->preOrder->token = $this->order->generateOrderHash(
  197. $this->preOrder->id_pedido_comercio,
  198. $this->preOrder->monto_total,
  199. $this->order->privateKey
  200. );
  201. $response = $this->runCurl($this->preOrder, self::URL_COMERCIOS);
  202. $arrayResponse = json_decode($response);
  203. //Verificar si hay error
  204. if(!$arrayResponse->respuesta){
  205. throw new Exception($arrayResponse->resultado);
  206. }
  207. $this->hashOrder = $arrayResponse->resultado[0]->data;
  208. $this->db->insertTransaction(
  209. $this->preOrder->id_pedido_comercio,
  210. $this->preOrder->tipo_pedido,
  211. $this->preOrder->monto_total,
  212. $this->hashOrder,
  213. $this->preOrder->fecha_maxima_pago,
  214. $this->preOrder->descripcion_resumen
  215. );
  216. $this->redirectToPagopar($this->hashOrder);
  217. }
  218. /**
  219. * Redirecciona a la página de Pagopar
  220. * @param string $hash Hash del pedido
  221. */
  222. public function redirectToPagopar($hash){
  223. $url = sprintf(self::URL_REDIRECT, $hash);
  224. //Redireccionamos a Pagopar
  225. header('Location: '. $url);
  226. exit();
  227. }
  228. /**
  229. * Inicia la transacción con Pagopar y si tiene éxito al generar el pedido con valores de prueba,
  230. * redirecciona a la página de pago de Pagopar.
  231. */
  232. public function newTestPagoparTransaction($public_key,$private_key,$seller_public_key){
  233. //Creamos el comprador
  234. $buyer = new BuyerPagopar();
  235. $buyer->name = 'Juan Perez';
  236. $buyer->cityId = 1;
  237. $buyer->tel = '0972200046';
  238. $buyer->typeDoc = 'CI';
  239. $buyer->doc = '352221';
  240. $buyer->addr = 'Mexico 840';
  241. $buyer->addRef = 'alado de credicentro';
  242. $buyer->addrCoo = '-25.2844638,-57.6480038';
  243. $buyer->ruc = null;
  244. $buyer->socialReason = null;
  245. //Agregamos el comprador
  246. $this->order->addPagoparBuyer($buyer);
  247. //Creamos los productos
  248. $item1 = new ItemPagopar();
  249. $item1->name = "Válido 1 persona";
  250. $item1->qty = 1;
  251. $item1->price = 1000;
  252. $item1->cityId = 1;
  253. $item1->desc = "producto";
  254. $item1->url_img = "http://www.clipartkid.com/images/318/tickets-for-the-film-festival-are-for-the-two-day-event-admission-is-lPOEYl-clipart.png";
  255. $item1->weight = '0.1';
  256. $item1->category = 3;
  257. $item1->productId = 100;
  258. $item1->sellerPhone = '0985885487';
  259. $item1->sellerAddress = 'dr paiva ca cssssom gaa';
  260. $item1->sellerAddressRef = '';
  261. $item1->sellerAddressCoo = '-28.75438,-57.1580038';
  262. $item1->sellerPublicKey = $seller_public_key;
  263. $item2 = new ItemPagopar();
  264. $item2->name = "Heladera";
  265. $item2->qty = 1;
  266. $item2->price = 785000;
  267. $item2->cityId = 1;
  268. $item2->desc = "producto";
  269. $item2->url_img = "https://cdn1.hendyla.com/archivos/imagenes/2017/04/09/publicacion-564c19b86b235526160f43483c76a69ee1a85c96c976c33e3e21ce6a5f9009b9-234_A.jpg";
  270. $item2->weight = '5.0';
  271. $item2->category = 8;//Cambiar a 8
  272. $item2->productId = 2;
  273. $item2->sellerPhone = '0985885487';
  274. $item2->sellerAddress = 'dr paiva ca cssssom gaa';
  275. $item2->sellerAddressRef = '';
  276. $item2->sellerAddressCoo = '-28.75438,-57.1580038';
  277. $item2->sellerPublicKey = $seller_public_key;
  278. //Agregamos los productos al pedido
  279. $this->order->addPagoparItem($item1);
  280. $this->order->addPagoparItem($item2);
  281. $this->order->publicKey = $public_key;
  282. $this->order->privateKey = $private_key;
  283. $this->order->typeOrder = 'VENTA-COMERCIO';
  284. $this->order->desc = 'Entrada Retiro';
  285. $this->order->periodOfDaysForPayment = 1;
  286. $this->order->periodOfHoursForPayment = 0;
  287. /*Calculamos el flete y... si es algún ítem tiene opcion_envio metodo_aex Y propio, entonces retornamos
  288. un json en el que el usuario debe seleccionar el método de pago para el ítem específico*/
  289. $json_pedido_con_flete = $this->getMethodsOfShipping();
  290. if( !$json_pedido_con_flete ){
  291. $this->newPagoparTransaction();
  292. }else{
  293. /*Seleccionamos los métodos de envío y después generamos la nueva transacción
  294. Método para seleccionar el envío, debe ser una pantalla intermedia
  295. y hay que llamar a la función newPagoparTransaction con un array como parámetro.
  296. El json debe tener esta forma:
  297. {
  298. id_producto_1 : metodo_seleccionado_1,
  299. id_producto_2 : metodo_seleccionado_2,
  300. ... : ...
  301. }
  302. */
  303. //$json_items_metodo_seleccionado = '{"100":"aex"}';
  304. //$pedidoPagopar->newPagoparTransaction($json_items_metodo_seleccionado);
  305. }
  306. }
  307. /**
  308. * Obtiene un JSON con el estado del pedido
  309. * @param int $id Id del pedido
  310. * @return JSON con el estado del Pedido
  311. * @throws Exception
  312. */
  313. public function getPagoparOrderStatus($id){
  314. $this->idOrder = $id;
  315. $orderData = $this->db->selectTransaction("id=$id");
  316. if($orderData){
  317. $this->hashOrder = $orderData['hash'];
  318. }else{
  319. throw new Exception("Hay un error con el hash");
  320. }
  321. $token = $this->generateToken(self::TOKEN_TIPO_CONSULTA);
  322. $args = ['hash_pedido'=>$this->hashOrder, 'token'=>$token, 'token_publico'=>$this->publicKey];
  323. $arrayResponse = $this->runCurl($args, self::URL_PEDIDOS);
  324. return $arrayResponse;
  325. }
  326. /**
  327. * Genera un Token para el pedido
  328. * @param string $typeOfToken Tipo de token generado
  329. * @return string Token generado
  330. */
  331. private function generateToken($typeOfToken){
  332. $key = ($this->privateKey)?$this->privateKey:$this->order->privateKey;
  333. return sha1($key.$typeOfToken);
  334. }
  335. #registrar usuario
  336. public function registrarUsuario(array $json){
  337. $url = self::URL_BASE.'usuario/1.1/registro';
  338. $args = $json;
  339. $arrayResponse = $this->runCurl($args, $url);
  340. return $arrayResponse;
  341. }
  342. }