domingo, 30 de noviembre de 2014

[PHP] E-Commerce Basico

En esta parte de PHP desarrollaremos un comercio electronico (e-commerce) que vende impresiones de arte. Por desgracia, escribir y explicar una aplicacion completa requeriria mas de las partes que se van a realizar, y algunos aspectos e-commerce como la forma de manejar dinero son particulares en cada sitio individual. Con estas limitaciones en mente, el objetivo en este capitulo es sobre la funcionalidad principal de un e-commerce: el diseño de la base de datos, llenar un catalogo como administrador, mostrar productos al publico, creacion de un carrito de compra y almacenamiento de las ordenes en la base de datos. Esto se basa en un ecommerce de impresiones de arte mas que nada diseñado por la base de datos, pero dentro de los articulos sera algo que no tendra nada que ver con ello.

Creando la base de datos

Con cualquier tipo de aplicacion de comercio electronico hay tres tipos de datos que se almacenan: la informacion del producto (lo que se vende), la informacion de los clientes (que esta haciendo compras), y la informacion del pedido (lo que se compro y por quien). Todo esto no lo explicaré, ya que esta mas que explicado en muchas de las partes de PHP aqui en este mismo BLOG.

1.- Iniciamos sesion en MySQL y creamos nuestra base de datos:

mysql> CREATE DATABASE ecommerce;
Query OK, 1 row affected (0.00 sec)

mysql> USE ecommerce;
Database changed
 

En estos casos tu puedes utilizar la linea de comandos para MySQL o simplemente utilizar un script en PHP tal como PHPMyAdmin.

2.- Creamos la tabla artistas:

mysql> CREATE TABLE artistas (
    -> artist_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> first_name VARCHAR(20) DEFAULT NULL,
    -> middle_name VARCHAR(20) DEFAULT NULL,
    -> last_name VARCHAR(40) NOT NULL,
    -> PRIMARY KEY (artist_id),
    -> UNIQUE full_name (last_name,first_name,middle_name)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.09 sec)

 

3.- Creamos la tabla prints:

mysql> CREATE TABLE prints (
    -> print_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> artist_id INT UNSIGNED NOT NULL,
    -> print_name VARCHAR(60) NOT NULL,
    -> price DECIMAL(6,2) UNSIGNED NOT NULL,
    -> size VARCHAR(60) DEFAULT NULL,
    -> description VARCHAR(255) DEFAULT NULL,
    -> image_name VARCHAR(60) NOT NULL,
    -> PRIMARY KEY (print_id),
    -> INDEX (artist_id),
    -> INDEX (print_name),
    -> INDEX (price)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.07 sec)


4.- Creamos la tabla clientes:

mysql> CREATE TABLE clientes (
    -> customer_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> email VARCHAR(60) NOT NULL,
    -> pass CHAR(40) NOT NULL,
    -> PRIMARY KEY (customer_id),
    -> UNIQUE (email),
    -> INDEX login (email,pass)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.07 sec)


5.- Crear la tabla Pedidos:

mysql> CREATE TABLE pedidos (
    -> order_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> customer_id INT UNSIGNED NOT NULL,
    -> total DECIMAL(10,2) UNSIGNED NOT NULL,
    -> order_date TIMESTAMP,
    -> PRIMARY KEY (order_id),
    -> INDEX (customer_id),
    -> INDEX (order_date)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.85 sec)


6.- Creamos la tabla contenido_pedidos:

mysql> CREATE TABLE contenido_pedidos (
    -> oc_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> order_id INT UNSIGNED NOT NULL,
    -> print_id INT UNSIGNED NOT NULL,
    -> quantity TINYINT UNSIGNED NOT NULL DEFAULT 1,
    -> price DECIMAL(6,2) UNSIGNED NOT NULL,
    -> ship_date DATETIME DEFAULT NULL,
    -> PRIMARY KEY (oc_id),
    -> INDEX (order_id),
    -> INDEX (print_id),
    -> INDEX (ship_date)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.79 sec)


Gracias a los indices es mas facil encontrar los datos mas rapidos (el uso de INDEX y PRIMARY KEY son un ejemplo claro). 

DECIMAL(m,n), donde m es el numero maximo de cifras en total (incluyendo la parte decimal) y n es el numero de cifras despues de la coma.  Tomando como ejemplo decimal(6,2):

123456
1234.56
123.456
12345.6
12.3456   

Los diferentes ENGINES del motor de base de datos, estamos utilizando MyISAM e InnoDB, ¿que hay de diferencia? MyISAM es mas rapido pero a diferencia de ello InnoDB permite utilizar transacciones.

Las transacciones son mas usadas para realizar consultas tipo INSERT, UPDATE, DELETE pero no son realizadas hasta que una instruccion COMMIT es ejecutada, esto es utilizado en sistemas ecommerce en caso de que una transaccion salga mal, existe una instruccion ROLLBACK que deshace todas las consultas en caso de error, de esta manera la base de datos no sera modificada.

Para iniciar una transaccion utilizamos:

START TRANSACTION;

Podemos realizar una consulta:

UPDATE cuenta SET dinero='10000000' WHERE id=2;

En caso de que por error ejecutemos esa consulta, y no nos acordamos cual era la cantidad que tenia, o haya ocurrido un error, es tan facil devolver todo a la normalidad, todo a como estaba antes utilizando:

ROLLBACK;

Si en otro caso si queremos que los cambios se guarden usamos:

COMMIT;

Podemos utilizar MySQL para que se ponga en modo de auto asignacion:

SET AUTOCOMMIT=0;

Con esto no es necesario que tu pongas START TRANSACTION y las consultas no seran permanentes hasta que tu pongas COMMIT.

Cuando se crea un Ecommerce siempre es mejor utilizar SSL (Secure Socket Layer) y realizar los pagos desde fuera de tu sitio web, confiandoles a otros sitios como Paypal por ejemplo las transacciones.

La parte administrativa

La parte administrativa de una aplicacion ecommerce, se compone principalmente por tres partes:

* productos (los articulos que se venden)
* clientes
* ordenes  

En este apartado creamos dos scripts para agregar productos al catalogo, como los productos que se venden son obras de arte, un script sera para rellenar la tabla artistas, y el otro para rellenar la tabla prints (que tiene una clave foranea a los artistas)

Debido a las limitaciones (constraints) de espacio, y el hecho de que sin un sistema de pago, este no sera un ejemplo completo de ecommerce de todas maneras. 





El primer script simplemente añade registros a la tabla artistas. El script presenta un formulario con tres entradas. Las entradas se validan y se añade el registro a la base de datos.

mysqli_connect.php

 <?php
 // Este archivo contiene informacion para acceder a la base de datos
 // Este archivo tambien establece una conexion a MySQL
 // selecciona la base de datos, y establece un encoding.

 // Establecemos la iformacion para acceder a la base de datos como constantes:
 define('DB_USER', 'tuusuario');
 define('DB_PASSWORD', 'tupass');
 define('DB_HOST', 'localhost');
 define('DB_NAME', 'ecommerce');

 // Creamos una conexion: 
 $dbc = @mysqli_connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME) OR die ('No pudo conectarse a MySQL: '.mysqli_connect_error());

 // Establecemos el encoding...
 mysqli_set_charset($dbc,'utf8');
?>


agregar_artista.php

 <!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Agregar un artista</title>
</head>
<body>
<?php
 // Esta pagina permite al administrador agregar un artista
 if($_SERVER['REQUEST_METHOD'] == 'POST'){
  // Manejamos el formulario

  // Validamos el nombre y el nombre en medio (ninguno es requerido).
  $fn = (!empty($_POST['first_name'])) ? trim($_POST['first_name']) : NULL;
  $mn = (!empty($_POST['middle_name'])) ? trim($_POST['middle_name']) : NULL;

  // Verificamos por el apellido...
  if (!empty($_POST['last_name'])) {
   $ln = trim($_POST['last_name']);

   // Agregamos un artista a la base de datos:
   require('./mysqli_connect.php');
   $q = 'INSERT INTO artistas (first_name, middle_name, last_name) VALUES (?,?,?)';
   $stmt = mysqli_prepare($dbc,$q);
   mysqli_stmt_bind_param($stmt,'sss',$fn,$mn,$ln);
   mysqli_stmt_execute($stmt);
   // Checamos los resultados...
   if(mysqli_stmt_affected_rows($stmt) == 1){
    echo '<p>Los artistas han sido agregados.</p>';
    $_POST = array();
   }else{ // Error!
    $error  = 'El artista no pudo ser agregado a la base de datos!.';
   }

   // Cerramos la conexion de esta consulta preparada_
   mysqli_stmt_close($stmt);
   mysqli_close($dbc);
  }else{ // No hay apellido 
   $error = 'Por favor introduzca el nombre del artista!';
  }
 } // Aqui termina el IF de envio

 // Checamos por los errores e imprimimos:
 if(isset($error)){
  echo '<h1>Error!</h1>
  <p style="font-weight: bold; color: #C00">'.$error. ' Por favor intente de nuevo</p>';
 }

 // Mostramos el formulario...
?>
<h1>Agregar un artista</h1>
<form action="" method="post">
 <fieldset><legend>Llena el formulario para agregar un artista:</legend>
 <p><b>Primer nombre:</b><input type="text" name="first_name" size="10" maxlength="20" value="<?php if(isset($_POST['first_name'])) echo $_POST['first_name']; ?>"></p>
 <p><b>Segundo nombre:</b><input type="text" name="middle_name" size="10" maxlength="20" value="<?php if(isset($_POST['middle_name'])) echo $_POST['middle_name']; ?>"></p>
 <p><b>Apellido:</b><input type="text" name="last_name" size="10" maxlength="40" value="<?php if(isset($_POST['last_name'])) echo $_POST['last_name']; ?>"></p>
 </fieldset>
 <div align="center"><input type="submit" name="submit" value="Enviar" /></div>
</form>
</body>
</html>


Aunque aqui no se realiza, seria recomendable utilizar usuarios de MySQL para darles los privilegios que necesitan, por ejemplo: SELECT, INSERT, UPDATE y DELETE. Mientras que un usuario publico solo necesita: SELECT, INSERT y UPDATE.

Las paginas administrativas siempre deben estar lo mas seguras posibles, esto indica alguna autentificacion HTTP usando apache, utilizar cookies o sesiones, etc.

Agregando impresiones




 
El segundo script a escribir es para agregar un nuevo producto (en concreto una impresion) a la base de datos. La pagina permitira al administrador seleccionar al artista de la base de datos, cargar una imagen, y poner los detalles de la impresion. La imagen se almacena en el servidor y el registro de la impresion se almacena en la base de datos. 

agregar_impresion.php

 <!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Agregar impresion</title>
</head>
<body>
<?php
 // Esta pagina permite al administrador agregar una impresion (del producto).
 require('./mysqli_connect.php');

 if($_SERVER['REQUEST_METHOD'] == 'POST'){
  // Manejamos el formulario.

  // Validamos los datos entrantes...
  $errors = array();

  // Verificamos por el nombre a imprimir:
  if(!empty($_POST['print_name'])){
   $pn = trim($_POST['print_name']);
  }else{
   $errors[] = 'Por favor introduzca el nombre a imprimir!'; 
  }

  // Verificamos para la imagen:
  if(is_uploaded_file($_FILES['image']['tmp_name'])){
   // Creamos un archivo temporal:
   $temp = 'uploads/'.md5($_FILES['image']['name']);

   //  moveos el archivo
   if (move_uploaded_file($_FILES['image']['tmp_name'], $temp)) {
    echo '<p>El archivo ha sido subido!</p>';

    // Establecemos una variable $i para el nombre de la imagen:
    $i = $_FILES['image']['name'];
   }else{
    // No pudo moverse el archivo.
    $errors[] = 'El archivo no pudo moverse!';
    $temp = $_FILES['image']['tmp_name'];
   }
  }else{
   // El archivo no se subio.
   $errors[] = 'El archivo no pudo subirse!';
   $temp = NULL;
  }
  // Verificamos el tamaño (no es requerido):
  $s = (!empty($_POST['size'])) ? trim($_POST['size']) : NULL;
  // Verficiamos por el precio:
  if(is_numeric($_POST['price']) && ($_POST['price'])>0){
   $p = (float) $_POST['price'];
  }else{
   $errors[] = 'Por favor introduzca el precio a imprimir!';
  }
  // Verificamos por la descripcion (no es requerido):
  $d = (!empty($_POST['description'])) ? trim($_POST['description']) : NULL;
  // Validamos el artista...
  if(isset($_POST['artist']) && filter_var($_POST['artist'],FILTER_VALIDATE_INT,array('min_range'=>1))){
   $a = $_POST['artist'];
  }else{
   // Ninguna artista fue seleccionado
   $errors[] = 'Por favor seleccione un artista a imprimir';
  }

  if(empty($errors)){  // si todo esta bien.
   // Agregamos lo que vamos a imprimir a la base de datos
   $q = 'INSERT INTO prints (artist_id, print_name, price, size, description, image_name) VALUES (?, ?, ?, ?, ?, ?)';
   $stmt = mysqli_prepare($dbc,$q);
   mysqli_stmt_bind_param($stmt,'isdsss', $a, $pn, $p, $s, $d, $i);
   mysqli_stmt_execute($stmt);

  // Verificamos los resultados...
  if(mysqli_stmt_affected_rows($stmt) == 1){
   // Imprimir un mensaje:
   echo '<p>La impresion ha sido agregada.</p>';

   // Renombramos la imagen:
   $id = mysqli_stmt_insert_id($stmt); // Obetenemos el id de la impresion
   rename($temp,"uploads/$id");

   // limpiamos $_POST:
   $_POST = array();
  }else{ // Error!
   echo '<p style="font-weight:bold;color:#C00">Tu envio no pudo ser procesado debido a un error del sistema.</p>';
  }
  mysqli_stmt_close($stmt);
 } // Fin de $errors IF.
 // Eliminamos el archivo subido si existe:
  if(isset($temp) && file_exists($temp) && is_file($temp)){
   unlink($temp);
  }
 } // Fin del IF de envio.

 // Verificamos por cualquier error y los imprimimos:
 if(!empty($errors) && is_array($errors)){
  echo '<h1>Error!</h1>
  <p style="font-weight:bold;color:#C00">Los siguientes errores han ocurrido:<br />';
  foreach($errors as $msg){
   echo " - $msg<br />\n";
  }
  echo 'Por favor reseleccione la imagen a imprimir e intente de nuevo.</p>';
 }
 // Mostramos el formulario...
?>
<h1>Agregar una impresion</h1>
<form enctype="multipart/form-data" action="agregar_impresion.php" method="post">
 <input type="hidden" name="MAX_FILE_SIZE" value="524288" />
 <fieldset><legend>Llena el formulario para agregar una impresion al catalogo:</legend>
 <p><b>Nombre de la impresion:</b> <input type="text" name="print_name" size="30" maxlength="60" value="<?php if(isset($_POST['print_name'])) echo htmlspecialchars($_POST['print_name']); ?>"></p>
 <p><b>Imagen:</b><input type="file" name="image" /></p>
 <p><b>Artista</b>
 <select name="artist">
  <option>Selecciona uno</option>
  <?php
   // Devolvemos todos los artistas y agregamos al menu desplegable
   $q = "SELECT artist_id,CONCAT_WS(' ',first_name,middle_name,last_name) FROM artistas ORDER BY last_name,first_name ASC";
   $r = mysqli_query($dbc,$q);
   if (mysqli_num_rows($r) > 0) {
    while($row = mysqli_fetch_array($r,MYSQLI_NUM)){
     echo "<option value=\"$row[0]\"";
     // Verificamos por pegajosidad:
     if(isset($_POST['existing']) && ($_POST['existing']) == $row[0]) echo 'selected="selected"';
     echo ">$row[1]</option>\n";
    }
   }else{
    echo '<option>Por favor agrega un nuevo artista primero.</option>';
   }
   mysqli_close($dbc); // Cerramos la conexion a la base de datos.
  ?>
 </select>
 </p>
 <p><b>Precio</b><input type="text" name="price" size="10" maxlength="10" value="<?php if(isset($_POST['price'])) echo $_POST['price']; ?>"><small>No incluya el signo dolar o comas.</small></p>
 <p><b>Tamaño:</b><input type="text" name="size" size="30" maxlength="60" value="<?php if(isset($_POST['size'])) echo htmlspecialchars($_POST['size']); ?>"></p>
 <p><b>Descripcion:</b><textarea name="description" cols="40" rows="5"><?php if(isset($_POST['description'])) echo $_POST['description']; ?></textarea></p>
 </fieldset>
 <div align="center"><input type="submit" name="submit" value="Enviar!" /></div>
</form>
</body>
</html>


Creando una plantilla 

Antes de llegar al corazon de la parte publica, se deben crear los archivos de encabezado y pie de pagina HTML necesarios. Vamos a hacer esto rapidamente.

header.html


 <?php
 // Esta pagina crea una sesion, en la pagina HTML.
 session_start(); // Iniciamos una sesion.
?>
<!doctype html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <style type="text/css">
  body{
   text-align: center;
  }
  h1{
   font-size: 15;
   font-family: "Comic Sans MS";
   color:green;
  }
  nav{
   margin-left: auto;
   margin-right: auto;
   width: 400px;
   background-color:black;
   border-radius: 5px;
  }
  nav a{
   text-decoration: none;
   color:white;
  }
  nav a:hover{
   text-decoration: underline;
  }
  nav ul li {
   list-style-type: none;
  }
  nav ul li{
   display: inline;
   padding-left: 5px;
  }
  footer{
   background-color: black;
   color: white;
   border-radius: 5px;
   text-align: center;
  }

 </style>
 <title><?php echo (isset($page_title)) ? $page_title : 'Bienvenido'; ?></title>
</head>
<body>
 <header>
  <h1>Crea una impresion!</h1>
 </header>
  <nav>
   <ul>
    <li><a href="index.php">Inicio</a></li>
    <li><a href="browse_prints.php">Ver impresiones</a></li>
    <li><a href="view_cart.php">Carro de compra</a></li>
   </ul>
  </nav>


footer.html



  <footer>
  <p>Copyright 2014</p>
 </footer>
</body>
</html>


index.php



 <?php
 // Esta es el menu para la pagina web
 
 // Establecemos el titulo del sitio e incluimos el header (cabecera):
 $page_title = 'Crea una impresion';
 include('header.html');
?>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab cumque nam illum dicta! Ut esse accusamus ullam saepe non, at maxime voluptatem ea velit quis nesciunt ab numquam id repellendus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Totam libero aliquid est temporibus. A nemo quisquam quos consectetur cupiditate, iusto error itaque, porro, ad eveniet facilis expedita ipsa incidunt assumenda!</p>
<?php
 include('footer.html');
?>



El catalogo de productos

Para que los clientes puedan comprar productos, necesitaran verlos primero. Para este fin, dos scripts representaran el catalogo productos. El primero browse_prints.php se mostrara una lista de las impresiones disponibles.Si un artista en particular ha sido seleccionado solo ese artista sera mostrado, de lo contrario se incluiran todas las impresiones.

El segundo script, view_print.php, se utiliza para mostrar la informacion de una sola impresion, incluyendo la imagen. En esta pagina los clientes encontraran un enlace Añadir al carrito, por lo que la impresion puede ser añadido a una cesta de compra.



browse_prints.php 


 <?php
 // Esta pagina muestra todas las impresiones (productos) disponibles

 // Establecemos el tituloe incluimos el header HTML:
 $page_title = "Productos disponibles";
 include('header.html');
 require('mysqli_connect.php');
 // Consulta por defecto para esta pagina:
 $q = "SELECT artistas.artist_id, CONCAT_WS(' ',first_name, middle_name, last_name) AS artist, print_name, price, description, print_id FROM artistas, prints WHERE artistas.artist_id = prints.artist_id ORDER BY artistas.last_name ASC, prints.print_name ASC";

 // ¿Estamos ante un artista en particular?
 if(isset($_GET['aid']) && filter_var($_GET['aid'],FILTER_VALIDATE_INT,array('min_range'=>1))){
  // Sobreescribimos la consulta:
  $q = "SELECT artistas.artist_id, CONCAT_WS(' ',first_name, middle_name, last_name) AS artist, print_name, price, description, print_id FROM artistas, prints WHERE artistas.artist_id = prints.artist_id AND prints.artist_id={$_GET['aid']} ORDER BY prints.print_name";

 }
 // Creamos el encabezado de la tabla
 echo '<table border="0" width="90%" cellspacing="3" align="center">
   <tr>
    <td align="left" width="20%"><b>Artista</b></td>
    <td align="left" width="20%"><b>Nombre del producto</b></td>
    <td align="left" width="40%"><b>Descripcion</b></td>
    <td align="right" width="20%"><b>Precio</b></td>
   </tr>';
 // Mostramos todos los productos, linkeados con URL:
 $r = mysqli_query($dbc,$q);
 while($row = mysqli_fetch_array($r,MYSQLI_ASSOC)){
  // Mostramos cada registro
  echo "\t<tr>
     <td align=\"left\"><a href=\"browse_prints.php?aid={$row['artist_id']}\">{$row['artist']}</a></td>
     <td align=\"left\"><a href=\"view_print.php?pid={$row['print_id']}\">{$row['print_name']}</a></td>
     <td align=\"left\">{$row['description']}</a></td>
     <td align=\"right\">{$row['price']}</a></td>
    </tr>\n";
 } // Terminamos el bucle while
 echo '</table>';
 mysqli_close($dbc);
 include('footer.html');
?>

Lo unico que hay que explicar aqui es la consulta SQL, donde utilizamos un JOIN utilizando una coma:

mysql> SELECT artistas.artist_id, CONCAT_WS(' ',first_name, middle_name, last_na
me) AS artist, print_name, price, description, print_id FROM artistas, prints WH
ERE artistas.artist_id = prints.artist_id ORDER BY artistas.last_name ASC, print
s.print_name ASC;
+-----------+----------------------+------------+--------+-------------------+----------+
| artist_id | artist               | print_name | price  | description       | print_id |
+-----------+----------------------+------------+--------+-------------------+----------+
|         1 | arthusu xd arthusuxd | Test       |  40.00 | This is a test    |  2       |
|         1 | arthusu xd arthusuxd | Watching   | 300.00 | i'm watching you! |  1       |
+-----------+----------------------+------------+--------+-------------------+----------+
2 rows in set (0.00 sec)


Y

mysql> SELECT artistas.artist_id, CONCAT_WS(' ',first_name, middle_name, last_na
me) AS artist, print_name, price, description, print_id FROM artistas JOIN prints WH
ERE artistas.artist_id = prints.artist_id ORDER BY artistas.last_name ASC, print
s.print_name ASC;
+-----------+----------------------+------------+--------+-------------------+----------+
| artist_id | artist               | print_name | price  | description       | print_id |
+-----------+----------------------+------------+--------+-------------------+----------+
|         1 | arthusu xd arthusuxd | Test       |  40.00 | This is a test    |  2       |
|         1 | arthusu xd arthusuxd | Watching   | 300.00 | i'm watching you! |  1       |
+-----------+----------------------+------------+--------+-------------------+----------+
2 rows in set (0.00 sec)

 


Devuelven el mismo resultado.

Para saber mas sobre JOINS puedes ver: http://arthusu.blogspot.mx/2013/03/mysql-minitutorial-parte-3.html

Otra accion que realiza este script es que si estamos en un artista en particular, muestra solo las impresiones (productos) de ese artista:



view_print.php

 <?php
 // Esta pagina muestra los detalles para un producto en particular
 
 $row = FALSE; // Asume que no hay nada!

 if(isset($_GET['pid']) && filter_var($_GET['pid'],FILTER_VALIDATE_INT,array('min_range'=>1))){
  // Estamos seguros de que hay un ID de impresion
  $pid = $_GET['pid'];

  // Obtenemos la informacion del producto:
  require('mysqli_connect.php');
  $q = "SELECT CONCAT_WS(' ',first_name,middle_name,last_name) AS artist, print_name, price, description, size, image_name FROM artistas, prints WHERE artistas.artist_id = prints.artist_id AND prints.print_id=$pid";
  $r = mysqli_query($dbc,$q);
  if(mysqli_num_rows($r) == 1){
   // Bueno vamos a ello!

   // Devolvemos toda la informacion:
   $row = mysqli_fetch_array($r,MYSQLI_ASSOC);

   // Comenzamos la pagina html:
   $page_title = $row['print_name'];
   include('header.html');

   // Mostramos la cabecera:
   echo "<div align=\"center\"><b>{$row['print_name']}</b> by {$row['artist']}<br />";

   // Imprimimos el tamaño o un mensaje predeterminado:
   echo (is_null($row['size'])) ? '(No hay informacion disponible sobre el tamaño)' : $row['size'];

   echo "<br />\${$row['price']}
   <a href=\"add_cart.php?pid=$pid\">Agregar a la cesta</a>
   </div><br />
   ";

   // Obtenems informacion de la imagen y la imprimimos:
   if($image = @getimagesize("uploads/$pid")){
    echo "<div align=\"center\"><img src=\"show_image.php?image=$pid&name=".urlencode($row['image_name'])."\" $image[3] alt=\"{$row['print_name']}\" /></div>\n";
   }else{
    echo "<div align=\"center\">La imagen no esta disponible</div>\n";
   }

   // Agregamos una descripcion o un mensaje por defecto:
   echo '<p align="center">'. ((is_null($row['description'])) ? '(No hay ninguna descripcion disponible)' : $row['description']) . '</p>'; 
  } // Terminamos el condicional de  mysqli_num_rows()

  mysqli_close($dbc);
 } // Fin del condicional $_GET['pid']

 if(!$row){ // Mostramos un mensaje de error.
  $page_title = 'Error';
  include('header.html');
  echo '<div align="center">Esta pagina ha sido provocado un error!</div>';
 }

 // Completamos la pagina:
 include('footer.html');
?>


En esta pagina hacemos una peticion para mostrar todos los datos que queremos de un solo producto:


En caso de que en la url, entren si que exista un solo articulo esto producira un error:



Si te das cuenta al leer el script view_print.php no se encuentra el enlace a la imagen que seria show_image.php, este será otro script con el cual mostraremos la imagen de nuestro producto.

Tambien se podria agregar un apartado para saber si el producto en particular se encuentra en STOCK y puede ser comprado, de otra manera mostrar un mensaje por defecto...


show_image.php



 <?php
 // Esta pagina devuelve y muestra una imagen

 // Variables banderas:
 $image = FALSE;
 $name = (!empty($_GET['name'])) ? $_GET['name'] : 'Imprimir imagen';

 // Verificamos por una imagen el valor en la URL:
 if(isset($_GET['image']) && filter_var($_GET['image'],FILTER_VALIDATE_INT,array('min_range'=>1))){
  // Ruta completa de la imagen:
  $image = 'uploads/'.$_GET['image'];

  // Verificamos que la imagen exista y que es un archivo:
  if(!file_exists($image) || (!is_file($image))){
   $image = FALSE;
  }
 } // Fin de el condicional $_GET['image']

 // Si ocurre un problema, usamos imagenes por defecto:
 if(!$image){
  $image = 'img_nodisponible.gif';
  $name = 'img_nodisponible.gif';
 }

 // Obtenemos la informacion sobre la imagen:
 $info = getimagesize($image);
 $fs = filesize($image);

 // Enviamos la informacion al navegador:
 header("Content-Type: {$info['mime']}\n");
 header("Content-Disposition: inline; filename=\"$name\"\n");
 header("Content-Length: $fs\n");

 // Enviamos el archivo:
 readfile($image);
?>

Con esto nos mostraria la imagen de la impresion:





En caso de que la imagen no se encuentre, mostrara una imagen por defecto para las imagenes que no existan:



Al momento de tu bajar la imagen va tener el nombre que le des en la url.


Si deseará buscar entre los productos, la estructura de la base  de datos crea una busqueda bastante facil. Como esta actualmente solo hay tres campos por buscar Nombre del producto, Descripcion, Apellido. Para ello podriamos utilizar una consulta con la siguiente sintaxis:

SELECT ... WHERE prints.description LIKE %palabraclave% OR prints.print_name LIKE %palabraclave%...

Se podria crear tambien una busqueda avanzada similar a IMDB


La cesta de compras

Una vez que haya creado el catalogo de productos, como los scripts anteriores, el script de cesta de la compra es realmente simple. El metodo elegido para este script es utilizar ID de producto, nombre, precio y cantidad en una sesion. Estos dos ejemplos siguientes proporcionaran toda la funcionalidad necesaria para la cesta de compra. El primer script, add_cart.php, se utilizara para agregar elementos a la cesta de compra. El segundo script, view_cart.php, tanto muestra el contenido de la cesta y permite al cliente paa actualizar la compra.


add_cart.php


 <?php
 // Esta pagina agrega las impresiones (productos) a la cesta de compras

 // Establecemos el titulo de la pagina e incluimos la cabecera:
 $page_title = 'Agregar a la cesta';
 include('header.html');

 if(isset($_GET['pid']) && filter_var($_GET['pid'],FILTER_VALIDATE_INT,array('min_range'=>1)) ){
  // Verificamos el id a imprimir.
  $pid = $_GET['pid'];

  // Verificamos si ya existe en la cesta este producto;
  // Si es asi incrementamos la cantidad:

  if(isset($_SESSION['cart'][$pid])){
   $_SESSION['cart'][$pid]['quantity']++; // Agregamos otra
   // Mostramos un mensaje:
   echo '<p>Otra copia de este producto ha sido agregado a tu cesta de compra.</p>';
  }else{
   // Un nuevo producto se agregara a la cesta.
   // Obtenemos el precio de la base de datos:
   require('mysqli_connect.php');
   // Conectamos a la base de datos.
   $q = "SELECT price FROM prints WHERE print_id=$pid";
   $r = mysqli_query($dbc,$q);
   if(mysqli_num_rows($r) == 1){
    // ID de Producto valido

    // Devolvemos la informacion.
    list($price) = mysqli_fetch_array($r,MYSQLI_NUM);

    // Agregamos a la cesta:
    $_SESSION['cart'][$pid] = array('quantity'=>1,'price'=>$price);

    // Mostramos un mensaje:
    echo '<p>El producto ha sido agregado a tu cesta de compra.</p>';
   }else{
    // ID de Producto no valido
    echo '<div align="center">Esta pagina no pudo ser accedida por un error!</div>';
   }
   mysqli_close($dbc);
  } // Fin del condicional isset($_SESSION['cart'][$pid])
 }else{
  // No se puede imprimir el ID del producto
  echo '<div align="center">Esta pagina no pudo ser accedida por un error!</div>; ';
 }

 include('footer.html');
?>

La cosa mas importante a almacenar en la cesta de compras es el ID del producto unico y la cantidad (quantity) de ese articulo. Todo lo demas incluyendo, el precio, se puede recuperar de la base de datos. 

El carro de compras se almacena en $_SESSION['cart'], no solo $_SESSION. Es de suponer que otros datos, como el ID del usuario de la base de datos, tambien sera almacenada en $_SESSION.


Ver el carrito de compra

E script view_cart.php sera mas complicado que add_cart.php por que tiene dos propositos. En primer lugar, se mostrara el contenido de la cesta en detalle. En segundo lugar, se le dara al cliente la posibilidad de actualizar el carro cambiando las cantidades de los productos en la misma (o eliminar un elemento haciendo su cantidad 0). Para cumplir ambos roles, el contenido de la compra se mostrara en un formulario que consigue enviarse de nuevo a esta misma pagina. Por ultimo esta pagina, se vinculara a un script checkout.php que pretende ser el primer paso en el proceso de pago.

view_cart.php


 <?php
 // Esta pagina muestra el contenido de la cesta de compras.
 // Esta pagina tambien deja al usuario actualizar el contenido de la cesta de compras.

 // Establecemos el titulo e incluimos la cabecera:
 
 $page_title = 'Ver tu carrito de compras';
 include('header.html');

 // Verificamos si el formulario ha sido enviado (para actualizar la cesta):
 if($_SERVER['REQUEST_METHOD'] == 'POST'){
  // Cambiamos cualquier cantidad:
  foreach($_POST['qty'] as $k => $v){
   // deben ser enteros!
   $pid = (int) $k;
   $qty = (int) $v;

   if($qty == 0){
    // Eliminamos.
    unset($_SESSION['cart'][$pid]);
   }elseif($qty > 0 ){
    // Cambiamos la cantidad
    $_SESSION['cart'][$pid]['quantity'] = $qty;
   }
  } // Fin del foreach
 } // Fin del condicional de envio

 // Mostramos la cesta y verificamos que no este vacia...
 if(!empty($_SESSION['cart'])){
  // Devolvemos toda la informacion de los productos en la cesta:
  require('mysqli_connect.php');
  $q = "SELECT print_id, CONCAT_WS(' ',first_name,middle_name,last_name) AS artist,print_name FROM artistas,prints WHERE artistas.artist_id = prints.artist_id AND prints.print_id IN (";
  foreach($_SESSION['cart'] as $pid => $value){
   $q .= $pid . ',';
  }
  $q = substr($q, 0,-1) . ') ORDER BY artistas.last_name ASC';
  $r = mysqli_query($dbc,$q);

  // Creamos un formulario y una tabla:
  echo '<form action="view_cart.php" method="post">
  <table border="0" width="90%" cellspacing="3" cellpadding="3" align="center">
  <tr>
   <td align="left" width="30%"><b>Artista</b></td>
   <td align="left" width="30%"><b>Producto</b></td>
   <td align="right" width="30%"><b>Precio</b></td>
   <td align="center" width="30%"><b>Qty</b></td>
   <td align="right" width="30%"><b>Precio Total</b></td>
  </tr>
  ';

  // Imprimimos cada elemento...
  $total = 0; // Costo total de la orden.
  while($row = mysqli_fetch_array($r,MYSQLI_ASSOC)){
   // calculamos el total y subtotal.
   $subtotal = $_SESSION['cart'][$row['print_id']]['quantity'] * $_SESSION['cart'][$row['print_id']]['price'];
   $total += $subtotal;

   // Imprimimos la fila:
   echo "\t<tr>
   <td align=\"left\">{$row['artist']}</td>
   <td align=\"left\">{$row['print_name']}</td>
   <td align=\"right\">\${$_SESSION['cart'][$row['print_id']]['price']}</td>
   <td align=\"center\"><input type=\"text\" size=\"3\" name=\"qty[{$row['print_id']}]\" value=\"{$_SESSION['cart'][$row['print_id']]['quantity']}\" /></td>
   <td align=\"right\">$".number_format($subtotal,2)."</td>";
  } // Fin del bucle while
  mysqli_close($dbc); // Cerramos la conexion a la base de datos

  // Imprimimos el total, cerramos la tabla, y el formulario:
  echo '<tr>
   <td colspan="4" align="right">
   <b>Total</b></td>
   <td align="right">$'.number_format($total,2).'</td>
   </tr>
   </table>
   <div align="center"><input type="submit" name="submit" value="Actualizar mi Cesta" /></div></form>
   <p align="center">Introduzca la cantidad 0 para remover un elemento.<br /><br />
   <a href="checkout.php">Pagar la cuenta</a></p>';
 }else{
  echo '<p>Tu cesta esta actualmente vacia.</p>';
 }

 include('footer.html');
?>

Un aspecto seguro de una aplicacion de comercio electronico (ecommerce) es como esta enviando y utilizando los datos. Por ejemplo, seria mucho menos seguro colocar el precio del producto por la URL, donde facilmente podria ser cambiado.

Los cambios realizados en las cantidades (Qty) puedes actualizar el carrito de compra, cambiando el subtotal y total.


Para eliminar los elementos de la cesta basta con establecer todos en 0.


Almacenar las ordenes

Despues de mostrar todos los productos en el catalogo, y despues de que el usuario ha llenado el carrito de compras, hay tres pasos finales:

* Comprobacion del usuario
* Almacenar la orden en la base de datos
* El cumplimiento de la orden

Ironicamente, el parcial mas importante (tomar el dinero del cliente), no se podria mostrar adecuadamente en este ejemplo basico ya que necesita de mas partes y es individual para cada sitio.
En este caso vamos a almacenar la informacion del pedido e introducirlos correctamente en la tabla contenido_pedidos utilizando transacciones.

checkout.php



 <?php
 // Esta pagina inserta la orden y la informacion dentro de una tabla.
 // Esta pagina puede venir despues de rellenar toda la informacion del cliente.
 // Esta pagina asume que ya has rellenado la informacion del cliente (y el dinero ha sido tomado).
 
 // Establecemos el titulo de la pagina e incluimos la cabecera:
 $page_title = "Confirmacion de Orden";
 include('header.html');

 // Asumimos que el cliente esta con sesion iniciada y que tiene su ID (identificador):

 $cid = 1; // Temporalmente.

 // Asumimos que esta pagina recibe la orden total:
 $total = 178.93; // Temporalmente.

 require('mysqli_connect.php');  // Conectamos a la base de datos

 // Ponemos OFF autocommit
 mysqli_autocommit($dbc,FALSE);

 // Agregamos una orden en la tabla orders:
 $q = "INSERT INTO pedidos (customer_id,total) VALUES ($cid,$total)";
 $r = mysqli_query($dbc,$q);
 if(mysqli_affected_rows($dbc) == 1){
  // Necesitamos el ID del pedido:
  $oid = mysqli_insert_id($dbc);

  // Insertamos el pedido especifico dentro de la base de datos...

  // Preparamos una consulta:
  $q = "INSERT INTO contenido_pedidos (order_id, print_id, quantity, price) VALUES (?,?,?,?)";
  $stmt = mysqli_prepare($dbc,$q);
  mysqli_stmt_bind_param($stmt,'iiid',$oid,$pid,$qty,$price);

  // Ejecutamos cada consulta; contando el total de afectadas:
  $affected = 0;
  foreach($_SESSION['cart'] as $pid => $item){
   $qty = $item['quantity'];
   $price = $item['price'];
   mysqli_stmt_execute($stmt);
   $affected += mysqli_stmt_affected_rows($stmt);
  }

  // Cerramos la consulta preparada:
  mysqli_stmt_close($stmt);

  // Reportamos lo ocurrido...
  if($affected == count($_SESSION['cart'])){ // Whohoo!
   // Realizamos la transaccion:
   mysqli_commit($dbc);
   // Limpiamos la cesta:
   unset($_SESSION['cart']);
   // Dejamos un mensaje al cliente:
   echo '<p>Gracias por su pedido. Seras notificado cuando se haya comprado el producto.</p>';

   // Enviamos un email de lo que sea:

  }else{ // Rollback y reportamos el problema:
   mysqli_rollback($dbc);
   echo '<p>Tu orden no pudo ser procesada por un error en el sistema. Tu debes ponerte en contacto para solucionar el problema. Pedimos disculpas por cualquier inconveniencia.</p>';
   // Enviamos la informacion del pedido para el administrador.
  }
 }else{ // Rollback y reportamos el problema.
  mysqli_rollback($dbc);
  echo '<p>Tu orden no pudo ser procesada por un error en el sistema. Tu debes ponerte en contacto para solucionar el problema. Pedimos disculpas por cualquier inconveniencia.</p>';
  // Enviamos la informacion del pedido para el administrador.
 }

 mysqli_close($dbc);

 include('footer.html');

?>

En un sitio trabajando realmente, usted debe asignar las variables $cliente y $total para trabajar con este script. Tambien deberia asegurarse de que la cesta no esta vacia antes de realizar la orden.





La orden puede ser vista desde MySQL.


Esta aqui termina este minitutorial basico para un sitio ecommerce, lo visto aqui no es algo "completo" sino algo para saber como funciona un sitio de comercio electronico, tambien nos hizo falta estructurar adecuadamente los archivos en sus carpetas, agregar HTTPS, agregar una compra en algun otro sitio seguro de pago, y muchas cosas mas, pero espero depues de haber realizado esto tengan un conocimiento basico de como es que trabaja un sitio de comercio electronico.




Puedes descagar todo completo desde Mediafire

No hay comentarios:

Publicar un comentario en la entrada