Skip to main content

sfcc-abandoned-cart

1. Descripción general

Según el pom.xml, el proyecto se describe como "SFCC API to receive abandonded carts". Es un servicio híbrido (API REST + tarea programada) que recibe eventos de carrito abandonado desde la web (Hawkers/Northweek), los deposita como ficheros en Google Cloud Storage, y cada 2 minutos los normaliza (locale, moneda, longitud de campos) y los envía a Salesforce.

2. Información técnica

CampoValor
artifactIdsfcc-abandoned-cart
groupIdcom.hawkersco
version1.0.25
Java25
Spring Boot4.0.6
Tipo de artefactojar (ejecutable, API web de larga duración)
MódulosNo aplica (proyecto de módulo único)

3. Arquitectura y diseño

  • .controllerSfccAbandonedCartInputController (recepción de eventos).
  • .componentSfccAbandonedCartOutputCronJob (procesamiento programado, lógica principal).
  • .configSecurityConfig (HTTP Basic + CORS), RestClientsConfig (@HttpExchange), SfccAbandonedCartConfig, FilterConfig.
  • .restclientAbandonedTokenCartClient (obtención de token OAuth de Salesforce), AbandonedCartClient (envío del carrito).
  • .entityAbandonedCartsRequest, AbandonedCartsResponse, SfccAbandonedCartEvent.
  • .daoUserOauth, RoleOauth (usuarios de Spring Security leídos desde PostgreSQL).
  • .filterMaliciousRequestFilter (ver hallazgo de seguridad en la sección 13).
flowchart TD
A["POST /push (Basic Auth, ROLE_ADMIN)"] -->|valida JSON| B[(GCS hawkersco_web_events<br/>topics/sfcc_abandoned_cart/)]
C["@Scheduled cada 2 min<br/>readCSVGoogleStorage"] -->|lee| D[(GCS send_carts_pending/)]
E["@Scheduled cada 2 min<br/>readJsonErrorGoogleStorage"] -->|lee| F[(GCS send_carts_errors/)]
C -->|normaliza locale/moneda/líneas/ciudad| G[AbandonedCartClient]
E --> G
G -->|POST con token Bearer| H[Salesforce]
G -->|éxito| I[Borra blob]
G -->|error clasificado| J[send_carts_errors_mail/ + Slack]
C -->|tras procesar| K[archiva en send_carts_processed/]

4. Dependencias principales

DependenciaPropósito
spring-boot-starter-web + spring-boot-starter-securityAPI REST con HTTP Basic
spring-boot-starter-data-jpa + postgresqlUsuarios/roles de Spring Security (UserOauth, RoleOauth)
org.apache.commons:commons-csvParseo de los ficheros CSV de carritos pendientes
com.sun.xml.bind:jaxb-core/implSoporte JAXB
org.json:jsonValidación de JSON de entrada
com.hawkersco:pi-function-commonsStorageUtils, DirectoryUtils, DateUtils
com.hawkersco:slack-clientNotificaciones de error
spring-boot-starter-test (test)JUnit 5 + Spring Test

5. API / Endpoints

MétodoRutaDescripciónAutenticación
GET/push/checkHealth checkPública
POST/pushRecibe un evento de carrito abandonado (JSON), lo valida y sube a GCSHTTP Basic, ROLE_ADMIN
POST/push/devEndpoint equivalente para pruebas/desarrolloNo configurado explícitamente en SecurityConfig (ver hallazgo en la sección 13)

6. Integraciones externas

SistemaProtocoloDirecciónDetalle
Google Cloud Storage (bucket hawkersco_web_events)API de GCSEntrante/SalienteAlmacenamiento de eventos entrantes, ficheros pendientes/error/procesados
SalesforceOAuth password grant + RESTSalienteAutenticación y envío del carrito abandonado
SlackHTTP (SlackClient)SalienteNotificación de errores clasificados (campo demasiado largo, moneda inválida, picklist inválido)
PostgreSQL (oauth)JDBCEntranteUsuarios/roles de Spring Security para el endpoint /push

7. Configuración

En producción (application-pro.properties) las credenciales llegan por variables de entorno inyectadas como Secret de Kubernetes; en local (application.properties) el repositorio contiene actualmente valores reales hardcodeados (ver alerta de seguridad).

ClaveDescripción
gcs.bucket.nameBucket de GCS (hawkersco_web_events)
spring.datasource.*Credenciales de la BD oauth (usuarios de Spring Security)
salesforce.credentials.token.*Credenciales OAuth password grant de Salesforce (usuario, contraseña, client-id/secret)
sfccabandonedcart.username / .passwordCredenciales HTTP Basic del endpoint /push
slack.client.url / .auth.token / .channel.idConfiguración de Slack
logging.level.org.springframework.securityNivel TRACE en el perfil local (ver hallazgo en la sección 13)

⚠️ Alerta de seguridad

El fichero src/main/resources/application.properties (perfil local) contiene actualmente credenciales reales en texto plano: contraseña de la base de datos PostgreSQL oauth, credenciales OAuth completas de Salesforce (usuario, contraseña, client-id y client-secret), las credenciales HTTP Basic del propio endpoint /push, y un token de bot de Slack. Ninguno de estos valores se ha reproducido en este documento. Se recomienda:

  1. Rotar las credenciales de Salesforce, la contraseña de BD, las credenciales HTTP Basic de /push y el token de Slack.
  2. Sustituir los valores hardcodeados de application.properties por credenciales de un entorno de desarrollo aislado.
  3. Revisar el historial de control de versiones, ya que estas credenciales pueden seguir expuestas en commits anteriores.

8. Persistencia

Base de datos PostgreSQL oauth, acceso vía JPA. spring.jpa.hibernate.ddl-auto=none. Entidades: UserOauth, RoleOauth (usadas por Spring Security para autenticar /push). No hay Flyway/Liquibase en este repositorio.

9. Procesos programados y mensajería

SfccAbandonedCartOutputCronJob — dos métodos @Scheduled(cron = "0 */2 * ? * *") (cada 2 minutos):

  • readCSVGoogleStorage: descarga los ficheros CSV pendientes (send_carts_pending/, ignorando directorios de metadatos de Spark y una carpeta legacy year=2020/), construye el request de carrito desde cada fila (mapeo posicional de columnas, con \N interpretado como valor vacío), normaliza el carrito (locale, moneda, máximo 5 líneas de producto, ciudades truncadas a 40 caracteres) y lo envía a Salesforce; tras procesar, archiva el blob en send_carts_processed/{fecha}/.
  • readJsonErrorGoogleStorage: reprocesa los ficheros previamente movidos a send_carts_errors/ (reintento de errores).
  • normalizeAndSendCart: en éxito borra el blob; en error, clasifica el mensaje (DUPLICATE_VALUE → borra sin más; STRING_TOO_LONG/moneda inválida/picklist inválido → notifica Slack y sube copia a send_carts_errors_mail/; cualquier otro caso también borra el blob tras el log).

10. Ejecución en local

Requisitos previos: JDK 25, Maven, acceso a la BD oauth, credenciales de aplicación por defecto de Google (GCS) y credenciales OAuth válidas de Salesforce.

# Compilar sin tests
mvn -B -DskipTests clean install

# Compilar con tests
mvn clean install

# Ejecutar la aplicación localmente (perfil dev, puerto 8080)
mvn spring-boot:run

# Ejecutar un test concreto
mvn test -Dtest=SfccAbandonedCartApplicationTests

# Ejecutar el JAR de producción
java -jar target/sfcc-abandoned-cart-*.jar

Verificación de que el servicio está operativo: GET /push/check (público).

11. Despliegue

  • Imagen: construida con jib-maven-plugin (base eclipse-temurin:25-jre, containerizingMode=packaged), publicada en europe-west3-docker.pkg.dev/pi-saldum/pi-repo/sfcc-abandoned-cart:<tag>. El CLAUDE.md menciona una imagen base eclipse-temurin:25-jdk-alpine, que no coincide con la configuración real vía Jib.
  • Orquestación: Kubernetes Deployment (no CronJob — la periodicidad la gestiona el @Scheduled interno) en el clúster GKE pi-cluster-hw, namespace pi.
  • CI/CD (Jenkins): pipeline que sustituye application-pro.properties por application.properties.

Job de Jenkins: https://jenkins-pi.hawkersco.net/job/sfcc-abandoned-cart/

12. Manejo de errores y logging

No hay un @RestControllerAdvice centralizado. processJsonFile/processCSVFile capturan excepciones genéricas por línea/registro y registran Level.SEVERE, sin interrumpir el resto del fichero. normalizeAndSendCart captura específicamente RestClientResponseException para notificar a Slack. Logging mediante java.util.logging.Logger estándar (consola), salvo el controlador de entrada que usa SLF4J.

13. Notas y consideraciones

  • MaliciousRequestFilter no bloquea nada, solo registra: el filtro comprueba si la URI contiene caracteres sospechosos ([, ], \) y, si los encuentra, únicamente hace logger.log(Level.SEVERE, ...)nunca interrumpe la cadena de filtros ni rechaza la petición; siempre llama a filterChain.doFilter(...) a continuación, coincida o no el patrón. El CLAUDE.md lo describe como que "blocks suspicious URI characters", lo cual no es cierto en el código actual: el filtro es puramente informativo y no aporta ninguna protección real frente a las peticiones que dice detectar.
  • /push/dev sin regla de seguridad explícita: SecurityConfig solo define reglas para /push/check (público) y /push (ROLE_ADMIN), pero el controlador expone también POST /push/dev sin que exista una regla requestMatchers específica para esa ruta ni un anyRequest() explícito al final de la cadena. Conviene verificar en tiempo de ejecución si esta ruta queda protegida por defecto o si permanece accesible sin autenticación.
  • Nivel de log TRACE para Spring Security en el perfil local: logging.level.org.springframework.security=TRACE puede volcar en el log detalles sensibles del proceso de autenticación (cabeceras, credenciales en tránsito); apropiado solo para depuración puntual, no como valor por defecto en el repositorio.
  • Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en application.properties.