f232f0fc5f
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).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
1.9 KiB
TypeScript
46 lines
1.9 KiB
TypeScript
/**
|
|
* Precios de la tienda en euros: conversión y mínimos de las pasarelas.
|
|
*
|
|
* Módulo PURO (sin acceso a BD), para que lo importen tanto la ruta de servidor
|
|
* como el componente de cliente y no puedan divergir: lo que enseña el carrito
|
|
* es exactamente lo que valida y cobra `/api/store/send`.
|
|
*/
|
|
|
|
// Duplicados a propósito de PD_PER_UNIT (lib/dpoints) y VP_PRICE_FACTOR
|
|
// (lib/pay-with-dpoints): esos módulos importan la BD y no pueden llegar al
|
|
// bundle del cliente. Mantener sincronizados (mismo criterio que
|
|
// PaymentMethodSelect). 100 PD = 1 €; los PV cuestan el doble: 200 PV = 1 €.
|
|
export const PD_PER_EUR = 100
|
|
export const PV_PER_EUR = 200
|
|
|
|
/**
|
|
* Importe mínimo por pasarela, en euros. Por debajo, la API de la pasarela
|
|
* rechaza el cobro, así que hay que impedirlo ANTES de crear el pedido.
|
|
*/
|
|
export const CARD_MIN_EUR: Record<'stripe' | 'sumup', number> = {
|
|
stripe: 1,
|
|
sumup: 1,
|
|
}
|
|
|
|
/**
|
|
* Máximo de copias de un mismo ítem por línea del carrito (igual que el MAX_QTY
|
|
* de send-gift). OJO con la diferencia: `copies` es cuántas VECES compras la
|
|
* línea, y no se confunde con `home_store_item.quantity`, que es el tamaño del
|
|
* lote que entrega cada copia (Paño de lino = 20). 2 copias de Paño de lino =
|
|
* 40 telas, y se envían como dos entradas `2589:20`.
|
|
*/
|
|
export const MAX_COPIES = 100
|
|
|
|
/** Euros de una línea, SIN redondear: un ítem en PV de precio impar cuesta medio céntimo. */
|
|
export function lineEur(price: number, currency: 'pd' | 'pv'): number {
|
|
return price / (currency === 'pv' ? PV_PER_EUR : PD_PER_EUR)
|
|
}
|
|
|
|
/**
|
|
* Coste del carrito en euros. Se redondea UNA vez, sobre el total: es el importe
|
|
* que se cobra, y NO la suma de las líneas redondeadas (que daría otro número).
|
|
*/
|
|
export function storeEuroTotal(pdTotal: number, vpTotal: number): number {
|
|
return Math.round((pdTotal / PD_PER_EUR + vpTotal / PV_PER_EUR) * 100) / 100
|
|
}
|