Pasar al contenido principal

Drupal 8 Commerce 2, crear un pedido por código

Viernes 13 de Marzo de 2020

En ciertas situaciones podemos necesitar crear un pedido mediante código propio. Por ejemplo, si queremos crear un pedido de forma rápida ante una acción del usuario sin que tenga que pasar por el proceso completo (buscar el producto, seleccionar unidades y añadirlo al carrito) o si queremos ocultar el interfaz de Commerce y usar un interfaz propio.

Dónde comenzar el proceso

Lo primero será determinar dónde añadir el código que crea el pedido. Esto dependerá de en respuesta a qué acción se creará el pedido. Lo normal será crear un servicio de Drupal propio que realice la tarea y llamar a ese servicio desde donde queramos crear el pedido. Usando el servicio podremos además usar fácilmente la inyección de dependencias, el método más adecuado para añadir dependencias externas a nuestra clase.

Crear el pedido

En el fichero que realiza el proceso de añadir el pedido deberemos importar las siguientes clases:

use Drupal\commerce_price\Price;
use Drupal\commerce_order\Entity\OrderItem;
use Drupal\commerce_order\Entity\Order;

Order item

Primero necesitamos crear un 'order item' (donde se determina qué producto se añade al pedido, la cantidad y el precio):

$order_item = OrderItem::create([
  'type' => 'awesome_order_item_type',
  'purchased_entity' => 1,
  'quantity' => 1,
  // Omit these lines to preserve original product price.
  'unit_price' => new Price(80, 'EUR'),
  'overridden_unit_price' => TRUE,
]);
$order_item->save();

Order

Luego podremos crear el pedido (order) y usar el order item previamente generado:

$order = Order::create([
  'type' => 'donation',
  'mail' => \Drupal::currentUser()->getEmail(),
  'uid' => \Drupal::currentUser()->id(),
  'store_id' => 1,
  'order_items' => [$order_item],
  'placed' => \Drupal::time()->getCurrentTime(),
  'payment_gateway' => 'example_payment',
  'checkout_step' => 'payment',
  'state' => 'draft',
]);

Se calcula el precio total y guardamos:

$order->recalculateTotalPrice();
$order->save();

Se añade un número de pedido basado en el identificador de pedido:

$order->set('order_number', $order->id());
$order->save();

Sesión de carrito para usuarios anónimos

En el caso de usuarios anónimos Commerce internamente se encarga de asociar el carrito con la sesión del usuario. Como lo hemos creado por código necesitamos explícitamente hacer el mismo proceso si queremos que el usuario pase por el checkout (lo que sería lo más lógico). Para ello, se necesita el servicio "commerce cart session service" para proveer del acceso necesario para acceder a la ruta del flujo de commerce checkout.

$cart_session = \Drupal::service('commerce_cart.cart_session');
if (\Drupal::currentUser()->isAnonymous()) {
  $cart_session->addCartId($order->id());
}

En caso de no hacerlo el usuario anónimo recibirá un error al acceder al pago.

Para usuarios registrados es necesario hacer nada.

Redirección

Tras crear el pedido lo normal será enviar el usuario al checkout. Como normalmente habremos creado el pedido en un el submit de un formulario como respuesta  a una acción del usuario, usamos en el flujo de envío de formulario el mecanismo de redirección, que sería de la siguiente manera:

$form_state->setRedirect('commerce_checkout.form', ['commerce_order' => $order->id()]);

Con eso el usuario estará en el flujo normal de checkout.

Snippets útiles relacionados

Para prevenir que el usuario añada múltiples pedidos a la sesión (es decir, si por ejemplo ya hay algún producto en el carrito porque se haya quedado un proceso anterior a medias), una posible estrategia sería mover al estado cancelado todos los pedidos no completados en la sesión de carrito (de tipo activo):

$cart_session = \Drupal::service('commerce_cart.cart_session');
$current_orders = $cart_session->getCartIds();
foreach ($current_orders as $order_id) {
  $order = Order::load($order_id);
  $order->state = 'canceled';
  $order->save();
  // Use just this to remove from cart session.
  $cart_session->deleteCartId($order_id);
}

Después de todo (tras redirigir en un "CheckoutPane" personalizado por ejemplo), se puede finalizar la sesión si la página de pedido completado no requiere ningún acceso especial y el usuario es anónimo, esto es, si se está usando una distinta a la predeterminada de Commerce, de esta manera el usuario puede seguir navegando por la web visualizando la versión cacheada de la misma. En otro caso, al haberse creado una sesión, Drupal no podrá usar las versiones cacheadas de la página, así que por rendimiento es recomendable hacerlo. 

if (\Drupal::currentUser()->isAnonymous()) {
  \Drupal::service('session_manager')->destroy();
}

 

Commerce