En mi tienda online tengo categorías de productos que llevan sus propios gastos de envío obligatorios y acumulables. Además estas categorias no suman para generar un envío GRATUITO, además…
El cliente
En el siguiente tutorial voy a explicar como he montado un plugin de gastos de envío personalizado para un cliente con WooCommerce.
Cuando el cliente me explico como se tenían que gestionar estos gastos de envío, estuve buscando una solución mediante un plugin pero no encontré nada que se adaptase a todas las necesidades del proyecto o por lo menos yo no lo encontré.
Las casuísticas de está tienda online me llevaron a querer programar una solución a medida, liviana, personalizable y lo mejor de todo con posibilidad de extender su funcionalidad.
He optado por extender una clase de WooCommerce como es WC_Shipping_Method
. Se utiliza para crear nuevos métodos de envío y me va a permitir tener todo el control y personalizaciones como:
- Tarifas de envío dinámicas: Calculando el costo de envío basado en ciertos criterios (peso, destino, categorías, almacén etc.).
- Restricciones geográficas: Limitando el envío a ciertas regiones o países.
- Condiciones adicionales: Aplicando reglas adicionales, como envío gratuito por encima de cierto monto de compra.
Estos son algunos de los métodos y propiedades más comunes de la clase WC_Shipping_Method
:
Propiedades:
id
: Identificador único del método de envío.title
: El título del método de envío que se muestra al usuario.method_title
: Un título más descriptivo usado en el panel de administración de WooCommerce.method_description
: Descripción del método de envío, que explica su funcionalidad.enabled
: Indica si el método de envío está habilitado (yes
ono
).supports
: Un array que indica qué características soporta este método de envío, comoshipping-zones
oinstance-settings
Métodos:
__construct()
: El constructor de la clase. Se utiliza para establecer propiedades iniciales y configurar el método de envío.is_available($package)
: Verifica si el método de envío está disponible para el paquete de productos actual. Puedes usar este método para definir la lógica que decide si el método está disponible en ciertas condiciones.calculate_shipping($package)
: Este método es donde se calcula la tarifa de envío. El$package
es un array que contiene detalles sobre los productos que se están enviando, como peso, dimensiones y destino.init()
: Inicializa el método de envío. Aquí es donde se cargan configuraciones específicas.init_form_fields()
: Define los campos de formulario que se mostrarán en la página de configuración del método de envío en el panel de administración.process_admin_options()
: Maneja la lógica para procesar los valores de configuración del método de envío cuando se guarda la configuración en el panel de administración.validate_*_field()
: Métodos de validación para campos específicos del formulario de configuración.
Te animo a consultar la documentación de WooCommerce para seguir el tutorial, en el cual se indica muy bien como se han de organizar las funciones.
¿Quieres que trabajemos juntos?
Me especializo en desarrollar sitios webs avanzados en WordPress, programando soluciones personalizadas para proyectos web.
Mi método de envío personalizado (Plugin)
A continuación vas a ver el código que he utilizado para mi plugin, siéntete libre de copiarlo, modificarlo y mejorarlo para tus clientes. Te voy a explicar las distintas partes de mi función.
- Lo primero que te encuentras son los comentarios del plugin. Esenciales si lo que necesitas es crear un plugin para WordPress con todas sus ventajas de instalar, activar, desactivar o eliminar.
- El primer condicional comprueba si tienes instalado WooCommerce, en caso de que no esté instalado no va a ejecutar la función principal. No tiene sentido, estamos extendiendo una Clase de WooCommerce.
- Seguidamente vamos extender la Clase
WC_Shipping_Method
, es donde vamos a realizar toda la magia del plugin. - Como he comentado antes vamos a tener una función
__construct()
donde vamos a establecer propiedades iniciales, establecer los campos personalizados y configurar el método de envío. - Ahora dentro de la función
init_form_fields()
vamos a definir los campos del formulario que se mostrarán en la página de configuración del método de envío en el panel de administración. Hacemos uso de WooCommerce settings API. - Almacenamos en una variable
$categories_options
un array con todas las categorías de producto, para luego utilizar en campo multiselect como opciones. - Creamos todos los campos personalizados que vayamos a necesitar y que se van a almacenar en sus respectivas variables, para realizar tantas comprobaciones o logia personalizada necesitemos.
- En el siguiente método llamado
calculate_shipping($package)
definimos todas las variables y nos preparamos para calcular los métodos de envío. Recorremos todos los productos del carrito teniendo en cuenta las distintas casuísticas en el proceso de compra.
Nuestro proyecto requiere que:
- Se suman 25€ siempre que el carrito contenga alguna de las categorías seleccionadas en el grupo que pertenece a municiones. (El envío de estos productos se hacen de forma independiente).
- Se suman otros 25€ cuando en el carrito haya algún producto de las categorías seleccionadas en el grupo perteneciente a armas. (El envío de estos productos se hacen de forma independiente).
- Los denominados productos básicos son aquellos que no entran dentro de ningún grupo (Municiones y armas) estos tienen unos gastos de envío independientes de 18€.
- Cuando los productos básicos superan o igualan el monto total de 100€, pasan a estado de envío Gratuito. (NO excluye los gastos de munición y armas).
- Existe una comprobación inicial para los productos denominados básicos, si el usuario ha marcado la opción de «Recogida en tienda», automáticamente estos pasan a ser con envío Gratis. De nuevo no excluye los gastos si el pedido contiene munición y/o armas.
- Por último vamos a registrar las tarifas y realizar las funciones necesarias para calcular las tasas de impuesto etc.
/*
Plugin Name: Custom Shipping Plugin
Plugin URI: https://yourwebsite.com/
Description: Plugin de método de envío personalizado para WooCommerce
Version: 1.0.0
Author: Tu Nombre
Author URI: https://yourwebsite.com/
*/
if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
function custom_shipping_method_init() {
if (!class_exists('WC_Custom_Shipping_Method')) {
class WC_Custom_Shipping_Method extends WC_Shipping_Method {
public function __construct() {
$this->id = 'custom_shipping_method';
$this->method_title = __('Método de Envío Personalizado', 'woocommerce');
$this->method_description = __('Descripción del Método de Envío Personalizado', 'woocommerce');
$this->enabled = "yes";
$this->title = "Gastos de Envío";
$this->init();
$this->title_field = $this->get_option('title_field');
$this->product_categories = $this->get_option('product_categories', array());
$this->product_categories_2 = $this->get_option('product_categories_2', array());
$this->text_field = $this->get_option('text_field', 18);
$this->text_field_1 = $this->get_option('text_field_1', 100);
$this->text_field_2 = $this->get_option('text_field_2', 25);
$this->text_field_3 = $this->get_option('text_field_3', 25);
}
function init() {
$this->init_form_fields();
$this->init_settings();
add_action('woocommerce_update_options_shipping_' . $this->id, array($this, 'process_admin_options'));
}
function init_form_fields() {
$product_categories = get_terms(array(
'taxonomy' => 'product_cat',
'hide_empty' => false,
));
$categories_options = array();
foreach ($product_categories as $category) {
$categories_options[$category->term_id] = $category->name;
}
$this->form_fields = array(
'enabled' => array(
'title' => __('Habilitar/Deshabilitar', 'woocommerce'),
'type' => 'checkbox',
'label' => __('Habilitar este método de envío', 'woocommerce'),
'default' => 'yes',
),
'title_field' => array(
'title' => __('Título del Método', 'woocommerce'),
'type' => 'text',
'description' => __('Esto controla el título que el usuario ve durante el pago.', 'woocommerce'),
'default' => __('Envío Personalizado', 'woocommerce'),
'desc_tip' => true,
),
'text_field' => array(
'title' => __('Gastos de envío Básicos', 'woocommerce'),
'type' => 'text',
'description' => __('Introduce los gastos de envío básicos de la tienda.', 'woocommerce'),
'default' => 18,
'desc_tip' => true,
),
'text_field_1' => array(
'title' => __('Envío Gratis a partir de', 'woocommerce'),
'type' => 'text',
'description' => __('Introduce el gasto mínimo del carrito para obtener un envío GRATIS.', 'woocommerce'),
'default' => 100,
'desc_tip' => true,
),
'text_field_2' => array(
'title' => __('Gastos de envío para Munición', 'woocommerce'),
'type' => 'text',
'description' => __('Estos gastos de envío se suman siempre que haya productos de Munición en el carrito.', 'woocommerce'),
'default' => 25,
'desc_tip' => true,
),
'text_field_3' => array(
'title' => __('Gastos de envío para Armas', 'woocommerce'),
'type' => 'text',
'description' => __('Estos gastos de envío se suman siempre que haya productos tipo Arma en el carrito.', 'woocommerce'),
'default' => 25,
'desc_tip' => true,
),
'product_categories' => array(
'title' => __('Categorías de Productos para Munición', 'woocommerce'),
'type' => 'multiselect',
'class' => 'wc-enhanced-select',
'description' => __('Seleccione las categorías de productos de munición aplicables para este método de envío.', 'woocommerce'),
'options' => $categories_options,
'default' => '',
'desc_tip' => true,
),
'product_categories_2' => array(
'title' => __('Categorías de Productos para Armas', 'woocommerce'),
'type' => 'multiselect',
'class' => 'wc-enhanced-select',
'description' => __('Seleccione las categorías de productos de armas aplicables para este método de envío.', 'woocommerce'),
'options' => $categories_options,
'default' => '',
'desc_tip' => true,
),
);
}
public function calculate_shipping($package = array()) {
$group_1_categories = $this->product_categories; // Categorías de munición
$group_2_categories = $this->product_categories_2; // Categorías de armas
$group_1_category_slugs = array();
$group_2_category_slugs = array();
foreach ($group_1_categories as $category_id) {
$category = get_term_by('id', $category_id, 'product_cat');
if ($category) {
$group_1_category_slugs[] = $category->slug;
}
}
foreach ($group_2_categories as $category_id) {
$category = get_term_by('id', $category_id, 'product_cat');
if ($category) {
$group_2_category_slugs[] = $category->slug;
}
}
$group_1_shipping_cost = floatval($this->text_field_2); // Munición
$group_2_shipping_cost = floatval($this->text_field_3); // Armas
$general_shipping_cost = floatval($this->text_field); // Básico
$free_shipping_threshold = floatval($this->text_field_1); // Umbral para Envío Gratis
$group_1_found = false;
$group_2_found = false;
$non_special_categories_total = 0;
foreach (WC()->cart->get_cart() as $cart_item) {
$product = $cart_item['data'];
$terms = get_the_terms($product->get_id(), 'product_cat');
$product_price = $cart_item['line_total'] + $cart_item['line_tax']; // Precio del producto incluyendo impuestos
if ($terms && !is_wp_error($terms)) {
$is_special_category = false;
foreach ($terms as $term) {
if (in_array($term->slug, $group_1_category_slugs)) {
$group_1_found = true;
$is_special_category = true;
}
if (in_array($term->slug, $group_2_category_slugs)) {
$group_2_found = true;
$is_special_category = true;
}
}
if (!$is_special_category) {
$non_special_categories_total += $product_price;
}
}
}
$shipping_cost = 0;
$breakdown = array();
$is_local_pickup = $this->is_local_pickup_selected();
if ($non_special_categories_total > 0) {
if ($non_special_categories_total < $free_shipping_threshold) {
$shipping_cost += $general_shipping_cost;
$breakdown[] = sprintf(__('Gastos de Envío Básicos: %s', 'woocommerce'), wc_price($general_shipping_cost));
}
}
if ($group_1_found) {
$shipping_cost += $group_1_shipping_cost;
$breakdown[] = sprintf(__('Gastos de envío para Munición: %s', 'woocommerce'), wc_price($group_1_shipping_cost));
}
if ($group_2_found) {
$shipping_cost += $group_2_shipping_cost;
$breakdown[] = sprintf(__('Gastos de envío para Armas: %s', 'woocommerce'), wc_price($group_2_shipping_cost));
}
if ($is_local_pickup) {
if ($non_special_categories_total > 0) {
if ($non_special_categories_total >= $free_shipping_threshold) {
$shipping_cost = 0;
$breakdown[] = __('Recogida en tienda (gratis para productos básicos)', 'woocommerce');
} else {
$breakdown[] = sprintf(__('Recogida en tienda (gastos básicos): %s', 'woocommerce'), wc_price($general_shipping_cost));
}
}
} else {
if ($non_special_categories_total >= $free_shipping_threshold) {
if (!$group_1_found && !$group_2_found) {
$breakdown[] = __('Envío Gratis', 'woocommerce');
$shipping_cost = 0;
}
}
}
// Calcular la tarifa de envío final
$rate = array(
'id' => $this->id,
'label' => $this->title_field,
'cost' => $shipping_cost,
'taxes' => '',
'calc_tax' => 'per_order',
'meta_data' => array(
'breakdown' => implode(', ', $breakdown)
),
);
$this->add_rate($rate);
}
private function is_local_pickup_selected() {
$chosen_methods = WC()->session->get('chosen_shipping_methods');
if ($chosen_methods) {
foreach ($chosen_methods as $method) {
if (strpos($method, 'local_pickup') !== false) {
return true;
}
}
}
return false;
}
}
}
}
add_action('woocommerce_shipping_init', 'custom_shipping_method_init');
function add_custom_shipping_method($methods) {
$methods['custom_shipping_method'] = 'WC_Custom_Shipping_Method';
return $methods;
}
add_filter('woocommerce_shipping_methods', 'add_custom_shipping_method');
}
Si has llegado hasta aquí, ¡Enhorabuena! ya tienes tu método de envio dentro de WooCommerce / ajustes / envíos.
Comprueba que se han creado todos los campos, que puedes modificar su contenido y lo mas importante que se pueden guardar los cambios.
Realiza pruebas en la tienda comprobando todas las opciones permitidas que alterarán en total del gasto de envío.
Las siguientes funciones nos van a ayudar a desglosar los costes de gastos de envío, mostrándolos tanto en el carrito, checkout o en el email que le llega al comprador. Para evitar devoluciones es importante indicar bien a que corresponde cada gasto.
add_action( 'woocommerce_cart_totals_after_shipping', 'show_shipping_cost_breakdown' );
add_action( 'woocommerce_review_order_after_shipping', 'show_shipping_cost_breakdown' );
function show_shipping_cost_breakdown() {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping = isset( $chosen_methods[0] ) ? $chosen_methods[0] : '';
if ( $chosen_shipping === 'custom_shipping_method' ) {
$packages = WC()->shipping()->get_packages();
$package = reset( $packages );
if ( isset( $package['rates'][ $chosen_shipping ] ) ) {
$rate = $package['rates'][ $chosen_shipping ];
$breakdown = $rate->get_meta_data()['breakdown'];
echo '<tr class="shipping-breakdown"><th>' . __( 'Desglose de Envío:', 'woocommerce' ) . '</th><td>' . esc_html( $breakdown ) . '</td></tr>';
}
}
}
add_filter( 'woocommerce_order_shipping_to_display', 'add_shipping_breakdown_to_emails', 10, 2 );
function add_shipping_breakdown_to_emails( $shipping, $order ) {
$chosen_shipping = $order->get_shipping_method();
if ( $chosen_shipping === 'Gastos de Envío' ) {
$rates = $order->get_shipping_methods();
$rate = reset( $rates );
if ( isset( $rate->meta_data['breakdown'] ) ) {
$breakdown = $rate->meta_data['breakdown'];
$shipping .= '<br><small>' . __( 'Desglose de Envío:', 'woocommerce' ) . ' ' . esc_html( $breakdown ) . '</small>';
}
}
return $shipping;
}
¿Hacia dónde puede evolucionar el plugin?
- Incorpora precios de envío por países.
- Accede al peso y medidas para crear otras reglas de precios.
- Desarrolla un sistema de reglas para productos de forma individual.
- Añade avisos o notificaciones de WooCommerce.
- Introduce la variable de rol de usuario en las reglas. Ten distintos costes en función del rol del comprador.
Espero haberte ayudado a crear un sistema de gastos de envçios personalizado. Si tienes alguna pregunta no dudes en utilizar el sistema de comentarios.