Integración con Medusa
Cannahub utiliza Medusa v2 como backend de e-commerce. Esta guía documenta la arquitectura de integración, endpoints disponibles, y las convenciones utilizadas para extender Medusa con funcionalidad específica del dominio.
Qué es Medusa
Medusa es una plataforma de e-commerce headless y open-source que proporciona:
- Name
API RESTful- Type
- core
- Description
Endpoints completos para productos, órdenes, clientes y más.
- Name
Multi-tenancy- Type
- feature
- Description
Soporte para múltiples tiendas/organizaciones.
- Name
Extensibilidad- Type
- feature
- Description
Campos metadata flexibles para datos de dominio.
- Name
TypeScript- Type
- tech
- Description
SDK tipado y arquitectura moderna.
Stack de Medusa v2
// Backend
- Node.js + TypeScript
- PostgreSQL (database)
- Redis (cache/sessions)
// Arquitectura
- Admin API (/admin/*)
- Store API (/store/*)
- Auth API (/auth/*)
// Extensiones
- Custom metadata en todas las entidades
- Workflows para lógica compleja
- Event system para webhooks
Arquitectura de Integración
URLs Base
- Name
Producción- Type
- https://api.cannahub.tech
- Description
API de producción para aplicaciones en vivo.
- Name
Staging- Type
- https://staging-api.cannahub.tech
- Description
Entorno de pruebas pre-producción.
- Name
Desarrollo- Type
- http://localhost:9000
- Description
Servidor local de Medusa.
Configuración de cliente
// lib/medusa/client.ts
import Medusa from '@medusajs/medusa-js'
const MEDUSA_BACKEND_URL =
process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL
?? 'http://localhost:9000'
export const medusaClient = new Medusa({
baseUrl: MEDUSA_BACKEND_URL,
maxRetries: 3,
})
Estructura de la API
Admin API (/admin/*)
Endpoints para administradores y staff. Requieren autenticación con cannahubAdminToken.
| Endpoint | Descripción |
|---|---|
/admin/products | CRUD de productos |
/admin/customers | Gestión de clientes/miembros |
/admin/orders | Gestión de pedidos |
/admin/customer-groups | Membresías (customer groups) |
/admin/inventory-items | Control de inventario |
/admin/stock-locations | Ubicaciones de stock |
/admin/price-lists | Listas de precios por membresía |
/admin/fulfillments | Gestión de fulfillment |
/admin/payments | Captura de pagos |
Ejemplo: Listar productos
curl https://api.cannahub.tech/admin/products \
-H "Authorization: Bearer {admin_token}" \
-H "Content-Type: application/json"
Respuesta
{
"products": [
{
"id": "prod_01HQ8BD5G",
"title": "Blue Dream Indoor",
"status": "published",
"variants": [...],
"metadata": {
"strainId": "strain_blue_dream",
"isFlower": true
}
}
],
"count": 45,
"offset": 0,
"limit": 20
}
Store API (/store/*)
Endpoints públicos para clientes. Algunos requieren autenticación con cannahubToken.
| Endpoint | Auth | Descripción |
|---|---|---|
/store/products | No | Catálogo público |
/store/products/:id | No | Detalle de producto |
/store/carts | No | Crear carrito |
/store/carts/:id | Sí | Operaciones en carrito |
/store/carts/:id/complete | Sí | Completar orden |
/store/customers/me | Sí | Perfil del cliente |
/store/customers/me/orders | Sí | Historial de órdenes |
Ejemplo: Crear carrito
curl -X POST https://api.cannahub.tech/store/carts \
-H "Content-Type: application/json" \
-H "x-publishable-api-key: {api_key}" \
-d '{
"region_id": "reg_argentina",
"sales_channel_id": "sc_high_up"
}'
Respuesta
{
"cart": {
"id": "cart_01HQ8ABC123",
"region_id": "reg_argentina",
"items": [],
"total": 0
}
}
Auth API (/auth/*)
Endpoints de autenticación para diferentes tipos de usuarios.
| Endpoint | Descripción |
|---|---|
/auth/customer/emailpass | Login de miembro |
/auth/user/emailpass | Login de admin/staff |
/auth/customer/emailpass/register | Registro de miembro |
/auth/session | Verificar sesión activa |
Login de miembro
curl -X POST https://api.cannahub.tech/auth/customer/emailpass \
-H "Content-Type: application/json" \
-d '{
"email": "miembro@ejemplo.com",
"password": "contraseña_segura"
}'
Respuesta
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI..."
}
Autenticación
Tokens JWT
Cannahub utiliza JWT tokens almacenados en cookies HTTP-only para seguridad.
- Name
cannahubToken- Type
- cookie
- Description
Token de miembro. Expira en 7 días.
- Name
cannahubAdminToken- Type
- cookie
- Description
Token de admin/staff. Expira en 7 días.
Headers Requeridos
- Name
Authorization- Type
- header
- Description
Bearer {token}para endpoints autenticados.
- Name
x-publishable-api-key- Type
- header
- Description
API key pública para endpoints de store.
Configuración de headers
// api/medusa/config.ts
export function getMedusaHeaders(
isAdmin: boolean = false
) {
const token = isAdmin
? cookies().get('cannahubAdminToken')?.value
: cookies().get('cannahubToken')?.value
return {
'Authorization': token
? `Bearer ${token}`
: undefined,
'Content-Type': 'application/json',
'x-publishable-api-key':
process.env.MEDUSA_PUBLISHABLE_KEY,
}
}
Multi-Tenancy
Cannahub soporta múltiples organizaciones (clubs) usando características nativas de Medusa.
Identificación de Organización
- Name
Subdomain- Type
- identificador
- Description
high-up.cannahub.techidentifica al club.
- Name
Sales Channel- Type
- medusa
- Description
Cada club tiene su propio sales channel.
- Name
Stock Location- Type
- medusa
- Description
Inventario separado por ubicación.
- Name
Publishable API Key- Type
- medusa
- Description
Key asociada al sales channel.
Organizaciones soportadas
const ORGANIZATIONS = {
'high-up': {
salesChannelId: 'sc_high_up',
stockLocationId: 'sloc_high_up',
publishableKey: 'pk_high_up_xxx',
},
'don-marcelino': {
salesChannelId: 'sc_don_marcelino',
stockLocationId: 'sloc_don_marcelino',
publishableKey: 'pk_don_marcelino_xxx',
},
'circulo-rojo': {
salesChannelId: 'sc_circulo_rojo',
stockLocationId: 'sloc_circulo_rojo',
publishableKey: 'pk_circulo_rojo_xxx',
},
// ... más organizaciones
}
// Extracción desde request
function getOrganization(request: Request) {
const host = request.headers.get('host')
const subdomain = host?.split('.')[0]
return ORGANIZATIONS[subdomain]
}
Convenciones de Metadata
Medusa permite almacenar datos arbitrarios en el campo metadata de todas las entidades. Cannahub usa convenciones consistentes.
Customer Metadata
Estructura
interface CustomerMetadata {
// Estado del miembro
status: MemberStatus
role: Role
// Onboarding
onboardingStep: OnboardingStep
hasReprocann: boolean
// Información médica
medicalRecord: {
reprocannCode?: string
reprocannExpiration?: string
allergies: string[]
conditions: string[]
medications: string[]
emergencyContact: {
name: string
phone: string
relationship: string
}
}
// Membresía activa
membership: {
id: string
name: string
status: 'PAID' | 'PENDING' | 'EXPIRED'
expirationDate: string
}
// Datos personales extendidos
bornDate?: string
document?: string
avatar?: string
}
Product Metadata
Estructura
interface ProductMetadata {
// Identificación de tipo
isFlower: boolean
// Referencia a cepa (productos de flor)
strainId?: string
strainName?: string
strainType?: 'sativa' | 'indica' | 'hybrid'
// O cepa completa (legacy)
strain?: {
id: string
name: string
type: string
cannabinoids: {
thc: number
cbd: number
}
terpenes: string[]
effects: {
helpsWith: string[]
feelings: string[]
}
}
// Información adicional
cultivator?: string
harvestDate?: string
batchNumber?: string
}
Variant Metadata
Estructura
interface VariantMetadata {
// Peso/cantidad por unidad
weight: number // Gramos (para flores)
quantity: number // Unidades (para otros)
unit: 'grams' | 'ml' | 'units'
// Información de lote
batchId?: string
expirationDate?: string
}
Customer Group (Membership) Metadata
Estructura
interface CustomerGroupMetadata {
// Información de membresía
description: string
price: number
currency: string
// Beneficios y límites
benefits: string[]
limits?: {
monthlyPurchase?: number
productAccess?: string[]
}
// Configuración
priceListId: string
isActive: boolean
sortOrder: number
color?: string
}
Capa BFF (Backend for Frontend)
El BFF simplifica y optimiza las llamadas al backend.
Propósito
- Name
Agregación- Type
- optimization
- Description
Combina múltiples llamadas a Medusa en una sola respuesta.
- Name
Transformación- Type
- core
- Description
Convierte tipos de Medusa a tipos de Cannahub.
- Name
Caché- Type
- optimization
- Description
Cachea respuestas frecuentes.
- Name
Lógica de Negocio- Type
- core
- Description
Implementa reglas específicas del dominio.
Ejemplo de ruta BFF
// app/api/products/route.ts
import { getMedusaClient } from '@/lib/medusa'
import { parseMedusaProduct } from '@/lib/medusa/products'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const medusa = getMedusaClient()
// Llamada a Medusa
const { products, count } =
await medusa.admin.products.list({
status: 'published',
expand: 'variants,variants.prices',
})
// Transformación
const transformed = products.map(parseMedusaProduct)
// Respuesta simplificada
return NextResponse.json({
products: transformed,
count,
})
}
Rutas BFF Disponibles
| Ruta | Método | Descripción | Llamadas Reducidas |
|---|---|---|---|
/api/products | GET | Productos con cepas | 2 → 1 |
/api/products/:id/with-inventory | GET | Producto + inventario multi-ubicación | N+1 → 1 |
/api/strains | GET | Catálogo de cepas filtrable | - |
/api/memberships | GET | Membresías disponibles | 1 → 1 |
/api/dashboard/stats | GET | Estadísticas agregadas | 4 → 1 |
/api/orders/create-and-process | POST | Flujo completo de orden | 15+ → 1 |
Flujo de Creación de Orden
El flujo completo de orden requiere múltiples llamadas a Medusa que el BFF orquesta.
Manejo de Errores
Códigos de Error de Medusa
| Código | Significado |
|---|---|
400 | Solicitud inválida |
401 | No autenticado |
403 | Sin permisos |
404 | Recurso no encontrado |
409 | Conflicto (ej: email duplicado) |
422 | Entidad no procesable |
500 | Error interno |
Manejo de errores
// lib/medusa/error-handler.ts
export function handleMedusaError(
error: any
): ApiError {
if (error.response) {
const { status, data } = error.response
return {
code: status,
message: data.message
?? 'Error de servidor',
details: data.errors,
}
}
return {
code: 500,
message: 'Error de conexión con Medusa',
details: error.message,
}
}
Rate Limiting
Medusa implementa límites de tasa que Cannahub respeta y propaga.
- Name
Store endpoints- Type
- 1000 req/min
- Description
Endpoints públicos de tienda.
- Name
Admin endpoints- Type
- 500 req/min
- Description
Endpoints administrativos.
- Name
Auth endpoints- Type
- 10 req/min
- Description
Login para prevenir fuerza bruta.