allan de sydney
El contexto de mi desafío.
Estoy construyendo una tienda de WordPress / WooCommerce sin cabeza.
Si no está familiarizado con el concepto de un CMS sin cabeza, extraigo el contenido de la tienda (Productos y sus imágenes, texto) sobre la API REST de WordPress / WooCommerce. De esta manera, tengo el beneficio de un panel de CMS para mi cliente mientras puedo desarrollar en un lenguaje / biblioteca moderno, en mi caso, ¡Reaccionar!
Si es posible, me gustaría mantener el pago en WordPress/WooCommerce/PHP. Dependiendo del proyecto al que aplico este código/repetidor, sospecho que tendré que cortar y cambiar las pasarelas de pago, y hacer que esto sea seguro y compatible con PCI será mucho más fácil en PHP/WordPress: hay una gran cantidad de complementos para esto.
Esto significa que toda la tienda / front-end vivirá en React, con la excepción del carrito en el que el usuario será redirigido al front-end de CMS (WordPress, PHP) cuando desee completar su pedido.
El reto
Esto hace que la gestión de cookies para la sesión sea poco intuitiva y poco ortodoxa. Cuando se redirige al usuario de la tienda (sitio de React) al pago (sitio de WooCommerce/PHP), la sesión del carrito debe persistir entre los dos sitios.
Además, las solicitudes a WooCommerce se enrutan a través del servidor Node/Express en el que se encuentra mi cliente React. Hago esto porque quiero mantener oculta la dirección de WordPress, y así puedo aplicar GraphQL para limpiar mis solicitudes y respuestas. El problema es que en este proceso, las cookies se pierden porque mi cliente y mi CMS se comunican a través de un intermediario (mi servidor Node); necesito lógica adicional para administrar manualmente mis cookies.
El código
Cuando intento agregar algo a un carrito, desde un creador de acciones (estoy usando Redux para la administración del estado) presiono el punto final correspondiente de la API en mi servidor Node/Express:
export const addToCart = (productId, quantity) => async (dispatch) => {
dispatch({type: ADD_TO_CART});
try {
// Manually append cookies somewhere here
const payload = await axios.get(`${ROOT_API}/addtocart?productId=${productId}&quantity=${quantity}`, {
withCredentials: true
});
dispatch(addToSuccess(payload));
} catch (error) {
dispatch(addToCartFailure(error));
}
};
Luego, en el servidor Node/Express hago mi solicitud a WooCommerce:
app.get('/api/addtocart', async (req, res) => {
try {
// Manually retrieve & append cookies somewhere here
const productId = parseInt(req.query.productId);
const quantity = parseInt(req.query.quantity);
const response = await axios.post(`${WP_API}/wc/v2/cart/add`, {
product_id: productId,
quantity
});
return res.json(response.data);
} catch (error) {
// Handle error
return res.json(error);
}
});
allan de sydney
Con las pistas dadas por @TarunLalwani (¡un millón de gracias!) en sus comentarios, he logrado formular una solución.
Configuración de dominio de cookies
Como estaba trabajando con dos sitios separados, para que esto funcionara, tenía que asegurarme de que ambos estuvieran en el mismo dominio y que el dominio estuviera configurado en todas las cookies. Esto aseguró que las cookies se incluyeran en mis solicitudes entre el servidor Node / Express (sentado en, p. somedomain.com
) y el CMS de WooCommerce (sentado en, p. wp.somedomain.com
), en lugar de ser exclusivo de la wp.somedomain
subdominio Esto se logró estableciendo define( 'COOKIE_DOMAIN', 'somedomain.com' );
en mi wp-config.php
en el CMS.
Obtención y configuración manual de cookies
Mi código necesitaba una lógica adicional significativa para que se incluyeran las cookies, mientras que las solicitudes se enrutaban a través de mi servidor Node/Express a través del cliente.
En React, tenía que comprobar si existía la cookie y, si existía, tenía que enviarla en el encabezado de mi solicitud GET al servidor Node/Express.
import Cookies from 'js-cookie';
export const getSessionData = () => {
// WooCommerce session cookies are appended with a random hash.
// Here I am tracking down the key of the session cookie.
const cookies = Cookies.get();
if (cookies) {
const cookieKeys = Object.keys(cookies);
for (const key of cookieKeys) {
if (key.includes('wp_woocommerce_session_')) {
return `${key}=${Cookies.get(key)};`;
}
}
}
return false;
};
export const addToCart = (productId, quantity) => async (dispatch) => {
dispatch({type: ADD_TO_CART});
const sessionData = getSessionData();
const config = {};
if (sessionData) config['session-data'] = sessionData;
console.log('config', config);
try {
const payload = await axios.get(`${ROOT_API}/addtocart?productId=${productId}&quantity=${quantity}`, {
withCredentials: true,
headers: config
});
dispatch(addToSuccess(payload));
} catch (error) {
dispatch(addToCartFailure(error));
}
};
En el Servidor Node/Express tuve que comprobar si había incluido una cookie (guardada en req.headers
con la llave session-data
– era ilegal usarlo Cookie
como una clave aquí) del cliente, y si lo hice, añádalo al encabezado de mi solicitud que va a mi CMS.
Si no encontré una cookie adjunta, significaba que esta era la primera solicitud en la sesión, por lo que tuve que tomar manualmente la cookie de la respuesta que recibí del CMS y guardarla en el cliente (setCookieFunc
).
app.get('/api/addtocart', async (req, res) => {
try {
const productId = parseInt(req.query.productId);
const quantity = parseInt(req.query.quantity);
const sessionData = req.headers['session-data'];
const headers = {};
if (sessionData) headers.Cookie = sessionData;
const response = await axios.post(`${WP_API}/wc/v2/cart/add`, {
product_id: productId,
quantity
}, { headers });
if (!sessionData) {
const cookies = response.headers['set-cookie'];
const setCookieFunc = (cookie) => {
const [cookieKeyValue, ...cookieOptionsArr] = cookie.split('; ');
const cookieKey = cookieKeyValue.split('=')[0];
const cookieValue = decodeURIComponent(cookieKeyValue.split('=')[1]);
const cookieOptions = { };
cookieOptionsArr.forEach(option => (cookieOptions[option.split('=')[0]] = option.split('=')[1]));
if (cookieOptions.expires) {
const expires = new Date(cookieOptions.expires);
cookieOptions.expires = expires;
}
res.cookie(cookieKey, cookieValue, cookieOptions);
};
cookies.map(cookie => setCookieFunc(cookie));
}
return res.json(response.data);
} catch (error) {
// Handle error
return res.json(error);
}
});
No estoy seguro de si esta es la solución más elegante al problema, pero funcionó para mí.
notas
usé el js-cookie biblioteca para interactuar con cookies en mi cliente React.
trampas
Si está tratando de hacer que esto funcione en su entorno de desarrollo (usando localhost), hay algo de trabajo adicional por hacer. Ver Cookies en localhost con dominio explícito
¿Has probado a configurar
define( 'COOKIE_DOMAIN', 'somedomain.com' );
en tuswp-config.php
esto asegurará que wordpress genere cookies en el dominio principal en lugar del subdominio y ambos compartirán las cookies con algo adicional, por lo que no hay complejidad de código como la que tiene actualmente– Tarun Lalwani
15 de julio de 2018 a las 17:21
Gracias por el consejo @TarunLalwani: no lo he probado, ¡lo intentaré! Sería increíble si esto pudiera resolver todos mis problemas con las galletas. Mi duda inicial sería que A) Si WooCommerce observará o no esta opción, y B) Que todas las llamadas a la API REST de React a WordPress se realizan del lado del servidor (nodo, express), en lugar del lado del cliente (reaccionar). Me imagino que esto impediría la capacidad de axios (mi cliente http) para agregar automáticamente cookies relevantes del navegador a las solicitudes que estoy enviando a WordPress
– Allan de Sídney
15/07/2018 a las 21:36
A) Se puede disipar: encontré la confirmación de los desarrolladores de que: “WooCommerce usa la misma configuración de cookies que WordPress (por ejemplo, cookie de inicio de sesión de WordPress)”
– Allan de Sídney
15 de julio de 2018 a las 22:10
Para B, deberá enviar todas las cookies del navegador en su solicitud a WP y hacer lo mismo para las cookies de devolución también
– Tarun Lalwani
16 de julio de 2018 a las 2:46
Tiene razón, tengo que pasar mis cookies del cliente (Reaccionar) al servidor (Nodo) antes de presionar el CMS (WooCommerce a través de REST), y luego de manera similar, establecer cookies para el cliente desde dentro del nodo servidor. ¡Es complicado, pero ya casi está!
– Allan de Sídney
16 de julio de 2018 a las 10:18