Changelogs
store: devolver por Stripe/SumUp lo no entregado de una compra con tarjeta
15 de julio de 2026 a las 10:57adevopg · fb28e97
Faltaba la otra mitad de 0be3dc5: con saldo ya se reembolsaba lo que no llegaba a enviarse, pero con tarjeta el dinero lo tiene la pasarela y el cliente se quedaba pagando de más. Ambas tienen API de reembolso parcial, así que ahora `fulfill` puede pedir que se devuelva un importe y lo hace quien conoce la pasarela (lib/fulfill para Stripe, lib/sumup para SumUp). Para saber CUÁNTO devolver hay que saber qué costó cada entrada, así que el pedido pasa a guardarse como `itemId:qty:precio:moneda`. Releer el precio del catálogo al entregar no vale por dos razones: 114 item_id son ambiguos (el mismo objeto se vende a 50 PV y a 100 PD), y hay que devolver lo que se COBRÓ, no lo que valga el ítem el día de la entrega. Los pedidos con el formato viejo se entregan igual, pero no se puede calcular su reembolso: se registra para hacerlo a mano. `fulfill` devuelve ahora `boolean | {ok, refundEur}`; los servicios que no entregan a medias siguen devolviendo boolean y no se tocan. Sobre SumUp, todo comprobado contra su API en sandbox y nada de esto está en sitios obvios: - El reembolso NO va por referencia de checkout sino por transacción. - Hay que usar el endpoint de v1.0; el viejo `/v0.1/me/refund/{txn}` responde 409 a CUALQUIER reembolso parcial (el total sí funciona). - v1.0 quiere el importe en CÉNTIMOS y ENTERO, al revés que el resto de la API v0.1, que usa euros. Y mandar 0.10 en vez de 10 no da error: se trunca a 0, SumUp lo lee como "sin importe" y DEVUELVE EL PAGO ENTERO. Casi me lo comí. - Hay un mínimo por reembolso (20 cts en esta cuenta, la API lo dice en `min_refundable_amount`): por debajo responde 400 y se registra para hacerlo a mano. El host sale de SUMUP_API_BASE (por defecto api.sumup.com) en vez de ir a fuego. Verificado de punta a punta con pagos reales de prueba, parcheando el SOAP para que solo saliera el primer correo: - Stripe (sk_test): pagados 1,30 €, entregadas 12/13 -> reembolso de 0,10 €, status succeeded en la API de Stripe. - SumUp (sandbox): pagados 1,50 €, entregadas 12/15 -> evento REFUND 0.3 REFUNDED en la API de SumUp. De paso: el concepto del cobro decía "1 objeto" al comprar 15 copias (contaba líneas, no copias).
store: reembolsar solo lo no entregado si el envío falla a medias
15 de julio de 2026 a las 10:19adevopg · 0be3dc5
Un carrito de más de 12 entradas se envía en varios correos. Si fallaba el 3.º de 9, los 2 primeros ya estaban en el buzón del jugador y aun así se le devolvía el carrito ENTERO: se quedaba los objetos gratis. Con las cantidades por línea (f232f0f) llegar ahí es fácil: 100 copias son 9 correos. sendStoreItems devuelve ahora CUÁNTAS entradas entregó en vez de un booleano: un correo enviado no se puede deshacer, así que quien llama necesita saber dónde se cortó. Cada `.send items` es un correo, así que el troceo es atómico por correo: un chunk sale entero o no sale. purchaseStoreCart reembolsa solo las entradas que quedaron sin enviar (cada una lleva su precio y su moneda) y devuelve `partialDelivery`, con un mensaje que dice la verdad: parte llegó al correo del juego y solo se ha devuelto el resto. Si no sale ni un correo, sigue siendo el `deliveryFailed` de siempre con la devolución completa. En el pago con tarjeta no se puede hacer lo mismo (el dinero ya lo cobró la pasarela): fulfillStoreOrder devuelve false si no salió todo. No puede duplicar porque claimPaidCheckout reclama el pago una sola vez, pero por eso mismo tampoco hay reintento y un envío a medias hay que rescatarlo a mano desde home_store_order. Queda anotado en el código. Verificado de punta a punta, no solo razonado: con un parche temporal del SOAP que deja salir el 1.er correo y tumba el resto, 13 copias de un ítem de 10 PD (130 PD, 12+1 entradas) dejan el saldo en 500 -> 380, o sea 500-130+10: se devuelve SOLO la entrada que no salió y se cobran las 12 entregadas. El parche se revirtió y producción se reconstruyó limpia.
store: poder cambiar la cantidad de un mismo artículo en el carrito
15 de julio de 2026 a las 10:10adevopg · f232f0f
El carrito era un conjunto: añadir dos veces el mismo ítem no hacía nada y el botón se quedaba en "Añadido". Ahora cada línea lleva sus copias, editables con un <input number> en la columna Cant, y volver a pulsar Añadir suma una (mismo patrón que el carrito de send-gift, que ya lo hacía así). Ojo con los dos "cantidad" que conviven, que no son lo mismo: `home_store_item.quantity` es el LOTE que entrega cada copia (Paño de lino = 20) y `copies` es cuántas veces se compra la línea. Por eso la fila enseña "3 × 20" y el total de la columna Cant sigue siendo unidades (60), como en el original. Cada copia se manda al SOAP como una entrada propia (`2589:20 2589:20`), que es justo lo que ya sabía trocear sendStoreItems (12 por correo) y parsear fulfillStoreOrder: el formato del pedido de tarjeta no cambia. El servidor no se fía del cliente: priceStoreCart recibe {id, copies}, valida las copias (1..MAX_COPIES, como el MAX_QTY de send-gift), rechaza el carrito si algún id no existe y recalcula los totales como precio*copias contra la BD. Verificado contra la API con saldo de prueba, no solo en la interfaz: con 500 PD y un ítem de 200, 2 copias (400) pasan el cobro y 3 (600) dan insufficientPd. Si el servidor ignorase las copias ambas darían lo mismo, así que el corte exacto entre 2 y 3 prueba que multiplica. Comprobado también que el reembolso del deliveryFailed devuelve los 400 PD, y que copias=0 o 101 se rechazan. El CSS del input va con `#cart-list` por especificidad, no por gusto: el tema define `input[type=number] { width: 290px }` y se carga el último para ganar la cascada, así que un `.store-copies` a secas perdía y el input salía de 290px reventando la tabla (send-gift se libra porque usa estilo en línea).
store: precios en euros en el carrito y mínimo real de las pasarelas
15 de julio de 2026 a las 09:55adevopg · e500bd7
El carrito enseñaba PD/PV aunque eligieras tarjeta, y por defecto ya presuponía saldo. Ahora se muestran SIEMPRE las dos monedas y se apaga la que no se va a cobrar con el método elegido, así que no presupone nada ni se contradice. Los euros por línea van con hasta 3 decimales a propósito: un ítem en PV con precio impar cuesta medio céntimo (75 PV = 0,375 €), y redondear cada línea a 2 haría que las líneas no sumaran el total (0,38 + 0,38 = 0,76 contra un total de 0,75). El total sí va a céntimos: es el importe que se cobra de verdad. Y con mínimo 2 decimales, que es dinero: "1,50 €", no "1,5 €". El formateo va por Intl con el locale de la web, así que en español sale con coma; antes el selector interpolaba el número crudo y decía "0.75 €" con punto. Mínimos de las pasarelas: SumUp ya estaba en 1 € y Stripe estaba en 0,50 €, ahora también 1 €. Estaban sueltos en la ruta; se centralizan en `lib/store-pricing`, un módulo PURO que importan tanto la ruta como el componente, para que el carrito enseñe exactamente lo que se valida y se cobra. `storeEuroTotal` pasa a vivir ahí (una sola implementación) y lib/store lo envuelve con un guard: si alguien cambia PD_PER_UNIT o VP_PRICE_FACTOR sin tocar store-pricing, revienta al primer uso en vez de cobrar mal en silencio (esos módulos tocan BD y no pueden llegar al bundle del cliente, de ahí la duplicación, igual que en PaymentMethodSelect). Por debajo del mínimo, la opción de tarjeta se desactiva y dice "Mínimo 1,00 €" en vez de dejarte pulsar Enviar para que la API la rechace. El método efectivo se deriva en el render (si el carrito baja del mínimo con tarjeta ya elegida, cae al saldo) en vez de sincronizarlo con un setState en un efecto, que provocaría renders en cascada. Verificado en el navegador: con 0,375 € las dos tarjetas salen desactivadas y elegido el saldo; con 1,50 € se habilitan. Las dos líneas de 0,375 € suman el total de 0,75 €.
fix: el tooltip de wowhead salía con huecos enormes entre líneas
15 de julio de 2026 a las 09:18adevopg · 553a9cf
El tooltip se inyecta en el <body>, así que le caen encima las reglas globales del tema. La culpable era `th { height: 40px }` (global, viene tal cual del tema original): el tooltip usa <th> para la columna de la derecha (fase, tipo de arma, velocidad...), y cada una de esas filas se estiraba a 40px en vez de ~17. Cuatro filas de esas = los ~100px que sobraban. El store original no lo sufre porque su tooltip lo pinta otro script, con otro markup: es un problema que aparece al usar el tooltips.js de wowhead de verdad. Afectaba a TODAS las páginas con tooltips, no solo a la tienda. Medido contra una página desnuda con solo tooltips.js (misma versión, mismo ítem), que es la referencia de cómo debe verse: el tooltip pasa de 390px de alto a 289, exactamente el de la referencia. Las tablas del tema no se tocan: la regla va acotada a .wowhead-tooltip th, y el <th> del carrito sigue midiendo 40px. De paso, el tooltip no tenía fondo y se leía la página a través de él. No era cosa nuestra —pasa igual en la página desnuda—: universal.css solo le da layout y colores de calidad, y el fondo lo pone el CSS de wowhead.com, que aquí no se carga. Se le da el fondo y el borde de las cajas del tema.


