Capítulo pagos de la guía Neblla — productos, checkout Stripe, validación y consumo. El contrato base está en core.txt; el criterio de cuándo usar Neblla, en empezar.txt. # Compras Integración con Stripe. El desarrollador no procesa pagos, no monta endpoints de Stripe, no gestiona los webhooks de Stripe. Si quiere enterarse de cada compra en su propio sistema (un n8n, un CRM), el webhook saliente `purchase` de Neblla se lo manda (ver core.txt). Tres tipos de producto: `one_time`, `consumable` y `subscription` (mensual o anual, con prueba gratis si quieres). Los tres son **entitlements dentro de la app** — un permiso o un contador atado a un usuario — y un producto único o un consumible puede además ser un **fichero a la venta** (una foto en alta resolución, un PDF, un pack de texturas: "Ficheros a la venta" más abajo), tener **unidades a la venta** (una edición de 10, "Unidades a la venta") o ser **físico**, enviado con sus gastos de envío y seguido como pedido ("Productos físicos y pedidos"). En el **pre** de la app las compras van, por defecto, por el modo de prueba de Stripe: nada se cobra y pro no las cuenta (ver publicar.txt, «Las compras en pre»). ## API `neblla.shop.purchases` es un **objeto indexado por id de producto**, no un array — `{}` si el usuario no tiene nada. Comprueba la propiedad con `neblla.shop.purchases['pack_invierno']`, nunca con `.some(...)`. ```javascript neblla.shop.products(); // listar lo que tienes en venta neblla.shop.buy('skin_dorado'); // abre checkout de Stripe neblla.shop.buy('monedas_pack', 100); // consumible con cantidad neblla.shop.consume('monedas_pack', 10); // consumir unidades neblla.shop.buy('premium'); // suscripción: se renueva sola neblla.shop.manage('premium'); // cancelar, cambiar la tarjeta, ver facturas neblla.shop.validate(userId, 'vip_pass'); // verificar (también desde el servidor) ``` Firmas: - `neblla.shop.buy(productId, amount?, { country }?)` **(CLIENT; un invitado compra con la cuenta ligera, ver identidad.txt, "Cuenta ligera")** — abre el checkout de Stripe en una ventana. Llámalo dentro del gesto del usuario (un click) para que el navegador no bloquee la ventana. La promesa se resuelve cuando la ventana está abierta, NO cuando se ha pagado: lo comprado llega después con `neblla.shop.on('purchase')`. Si apagaste la cuenta ligera, a un invitado le pide hacer login en esa misma ventana y sigue con el pago en cuanto entra; si cierra la ventana sin entrar, rechaza con `login_required`. Rechaza con el código del servidor (`feature_disabled`, …). Si el navegador bloquea la ventana, rechaza con `popup_blocked` y trae la `url` del checkout: pinta un botón que la abra — ese clic es un gesto nuevo y el navegador sí lo deja. Los datos de tarjeta nunca pasan por el código de la app. Los productos no se pueden comprar hasta que la cuenta de cobros del dev (Stripe Connect) está activa. - `neblla.shop.consume(productId, amount)` **(CLIENT)** — consume unidades. - `neblla.shop.validate(userId, productId)` **(BOTH)** — verifica posesión. También se puede llamar desde `room.js`, útil para gatear acciones autoritarias detrás de tener un producto: por ejemplo, para entrar en la sala VIP, `room.js` comprueba que el usuario tiene `vip_pass` antes de dejarle pasar, sin que pueda falsificarlo desde su cliente. - `neblla.shop.products()` **(BOTH)** — lista productos. Uno que espera a la cuenta de cobros sale con `waiting: true`: píntalo como «próximamente», sin botón de compra; se pone a la venta solo en cuanto la cuenta está activa. - **Cuando una compra no sale.** Todo error de `shop.buy` y `shop.manage` trae un `code` para decidir qué decirle al usuario (un juego, una app o una web, el mismo `code`): `product_waiting` (aún no se vende: «pronto»), `product_not_published`, `product_not_found`, `invalid_amount` (con `min`, `max` o `step`), `payouts_not_ready` y `stripe_test_account_required` (el dev aún no puede cobrar), `not_connected`/`login_required` (que entre), `feature_disabled`, `popup_blocked` (con la `url`), `checkout_failed` (cualquier otro); en `manage`, `not_subscribed` y `portal_failed`. Con `test: true` el fallo es del modo de prueba. Ejemplo: `try { await neblla.shop.buy('bono_10') } catch (e) { aviso(e.code === 'product_waiting' ? 'Muy pronto a la venta' : e.code === 'popup_blocked' ? botonQueAbre(e.url) : 'No se ha podido abrir el pago') }`. - `neblla.shop.testMode` **(CLIENT)** — `null` cuando se cobra de verdad; `{ card }` cuando la app vende en el modo de prueba de Stripe (el pre, o una app de ejemplo, de slug acabado en `-example`): nada se cobra y `card` es la tarjeta de prueba que paga. Díselo al visitante junto al botón de compra. - `neblla.shop.manage(productId)` **(CLIENT, login)** — abre el portal de Stripe del suscriptor, donde cancela, cambia la tarjeta o descarga sus facturas. Mismas reglas de ventana que `shop.buy`. - `neblla.shop.orders()` **(CLIENT, login)** — los pedidos de productos físicos del propio usuario, los más nuevos primero: `[{ id, productId, productName, units, status, zone, shipping, carrier?, tracking?, trackingUrl?, history, withdrawal, refunded? }]` ("Productos físicos y pedidos" más abajo). - **Pasar lo comprado a otro usuario:** si pones un producto de un solo pago o consumible en `neblla_update_app({ trade: { products: [...] } })`, sus unidades se pueden intercambiar con `neblla.db.offer` (ver identidad.txt, «Intercambios entre usuarios»). El pago se queda con quien pagó: lo que recibe el otro no es una venta (no sale en Ventas ni se reembolsa), y los dos oyen `purchase` con `type: 'trade'`. Una suscripción no se intercambia nunca. ## Evento purchase Cuando la compra se confirma, el evento `neblla.shop.on('purchase', cb)` (`{ userId, productId, type, purchases }`) se dispara en los dispositivos del comprador, y solo en los suyos: lo que tiene cada usuario es suyo y no viaja a la sala. Útil para reaccionar en tiempo real (animación, desbloqueo del skin). Si otros tienen que ver algo que alguien compró (su skin, su insignia), que lo anuncie tu código con un mensaje y que `room.js` lo compruebe con `neblla.shop.validate(userId, productId)`: solo el servidor de la sala puede mirar lo de otro usuario. Desde un cliente, `validate` mira lo del propio usuario, y por otro responde `{ valid: false, error: 'not_allowed' }`. **Trampas.** El SDK compara de vez en cuando las compras del dispositivo con las del servidor: si tiene menos (una pestaña vieja), se corrige y ya está; si dice tener más de lo que compró, se corrige, el usuario recibe una marca (la columna de marcas del backoffice) y lo ves en el panel, Personas › Moderación, con lo que decía tener y lo que tiene de verdad. Es un aviso: no se le hace nada más. Si la venta se **reembolsa entera** desde Stripe, o el comprador abre una disputa y **la pierdes** (contracargo), la compra deja de contar en todas partes y, si el usuario está conectado, llega otro `shop.on('purchase')` con `type: 'revoked'` y los `purchases` ya actualizados: quita lo que desbloqueaba. Un reembolso parcial solo queda anotado; qué unidades cubre lo decides tú. Mientras una disputa está abierta, o si la ganas, el usuario conserva lo comprado. **Tus ventas, en el panel.** Dinero › Ventas lista cada compra con lo pagado, su estado y quién la hizo. Desde una venta puedes **reembolsarla** entera (vuelve el dinero en Stripe, con la comisión de Neblla, y la compra se retira al momento como arriba; una suscripción no se reembolsa ahí: se reembolsa o se cancela en tu panel de Stripe, y su venta trae el enlace, «Abrir en Stripe», `stripeUrl`) y abrir **el recibo o las facturas** de Stripe de esa compra (los enlaces que da Stripe; Neblla no guarda PDFs). Las ventas (con el id, el email y el nombre del comprador) y tus usuarios se descargan en **CSV**. Por el MCP, `neblla_sales`; por HTTP, `POST /api/backend/apps//sales` (y `/sales/csv`, `/sales//refund`, `/sales//documents`, `/users/csv`) con tu `authToken`. Propiedad: `neblla.shop.purchases` **(CLIENT)** — las compras del usuario. ## Suscripciones Para un plan premium que se paga cada mes o cada año. El producto lleva `type: 'subscription'`, el precio por periodo, `interval: 'month' | 'year'` y, si quieres, `trialDays` de prueba gratis. Se vende igual: `neblla.shop.buy('premium')`. - **Mientras está pagada (o en prueba),** `neblla.shop.purchases['premium']` existe, con `{ type: 'subscription', active: true, renewsAt, trialEndsAt?, cancelAtPeriodEnd }`, y `shop.validate` dice que sí, también desde `room.js`. - **Stripe cobra la renovación solo.** Si un cobro falla, Stripe reintenta durante unos días y el usuario conserva el acceso mientras tanto. - **Cuando termina** (la cancela y acaba el periodo pagado, o los reintentos fallan), deja de contar en todas partes y llega un `shop.on('purchase')` con `type: 'revoked'`, igual que un reembolso. - **El usuario gestiona la suya** con `neblla.shop.manage('premium')`: cancelar, cambiar la tarjeta, facturas. No tienes que montar nada. - **Tus avisos:** el webhook saliente `purchase` llega en el alta y en cada renovación, y `subscription_end` cuando termina (ver core.txt). **Un plan por equipo** (por ejemplo, 9 € al mes por equipo) no es otro tipo de producto: es la suscripción de quien paga más un grupo para su gente (ver identidad.txt, "Grupos y permisos"). Lo montas como quieras; un ejemplo, con tu n8n (o tu servidor) entre los webhooks y la API de administración: ```javascript // 1. Papeles y avisos. Los grupos los crea tu n8n, no el cliente. neblla_update_app({ roles: { titular: { tareas: 'read write' }, equipo: { tareas: 'read write' } }, groups: { invitable: ['equipo'], maxMembers: 10 }, webhooks: [ { event: 'purchase', secret: 'N8N', path: '/equipo-alta' }, { event: 'subscription_end', secret: 'N8N', path: '/equipo-baja' }, ], }) ``` - **Alta.** Llega `purchase` con `{ productId: 'equipo', userId }` (las renovaciones traen `renewal: true` y no tocas nada). Tu n8n crea el grupo y mete dentro al que paga, con `Authorization: Bearer ` (la clave vive en tu n8n, nunca en el cliente): ``` POST https://neblla.com/api/apps//groups/create { "group": "eq-" } POST https://neblla.com/api/apps//groups/add { "group": "eq-", "user": "", "role": "titular" } ``` - **El titular invita a su gente** desde tu app: ```javascript const mio = (await neblla.groups.mine()).find(g => g.role === 'titular'); const { url } = await neblla.groups.invite(mio.group, { role: 'equipo' }); ``` Y el equipo trabaja con filas del grupo: `neblla.db.save(datos, { table: 'tareas', group: mio.group, id })`. - **Baja.** Llega `subscription_end` con `{ productId, userId, reason }`. Tu n8n borra el grupo: `POST …/groups/delete { "group": "eq-" }`. Todos salen; las filas del equipo se quedan, pero nadie llega a ellas desde el cliente. Si vuelve a pagar, el alta crea el grupo con el mismo nombre y recupera esas filas; a su gente la invita otra vez. Otras formas valen igual: quitar solo al equipo y dejar al titular, o pasarlos a un papel de solo lectura (`groups/add` con otro `role`). ## Validación autoritaria `neblla.shop.validate` también se puede llamar desde `room.js` — útil para gatear acciones autoritarias detrás de la posesión de un producto. Ejemplo: para entrar a la sala VIP, `room.js` valida que el usuario tenga `vip_pass` antes de admitirlo, sin que pueda fingirlo desde su cliente. ## Schema de producto Al crear un producto desde el dashboard (o desde el MCP): ```javascript { name: { en: 'Gold skin', es: 'Skin dorado' }, // i18n map o string description: { en: '...', es: '...' }, // opcional image: 'https://.../skin.png', // opcional type: 'one_time' | 'consumable' | 'subscription', price: 499, // unidad más pequeña (céntimos en eur): one_time, o por periodo en subscription interval: 'month', // 'month' | 'year', sólo subscription trialDays: 7, // opcional, sólo subscription pricePerUnit: 10, // unidad más pequeña, sólo consumable step: 1, // tamaño del bundle (consumable) min: 5, max: 1000, // límites del slider (consumable); la compra mínima, 5 × 10, llega al mínimo de Stripe published: true, // visible en shop.products() currency: 'eur', // ISO-4217 (por defecto eur) defaultLang: 'en', // fallback i18n stock: 10, // opcional, one_time y consumable: unidades a la venta en total (null: sin límite) stockRefundReturns: true, // opcional: una unidad reembolsada vuelve a la venta (false en una edición numerada) shipping: { zones: [{ name: 'España', countries: ['ES'], amount: 1500 }] }, // opcional: un producto físico } ``` ## Ficheros a la venta Un **original privado** es un fichero de los medios de la app (`neblla.media`, referencia.txt) que solo descarga quien tiene derecho: «Agua nº 3» en alta resolución, las partituras de una academia en PDF, el pack de texturas HD de un juego, un ebook, un `wav`. De cualquier tipo: imágenes, PDF, EPUB, ZIP, sonido, vídeo. La lectura pública da solo sus variantes hasta el ancho que elige el dev (`publicWidth`, 1280 px por defecto, una vista previa que no se puede imprimir), nunca el original, que vive en un bucket aparte. - **Marcarlo:** `neblla_media` `upload` con `private: true` (y `publicWidth`), o `private` `{ ids | collection, private: true, publicWidth? }` después (Construir › Medios en el panel: «Original privado»). - **Qué lo desbloquea:** `neblla_media` `link` `{ productId, ids | collection }` (y `unlink`): un **producto único** lo desbloquea para siempre (un reembolso completo o una disputa perdida lo retira con la compra); una **suscripción**, mientras está viva (toda la biblioteca de una academia mientras paga). Un consumible no desbloquea ficheros. Y un **tipo de bono** con `unlocks` (core.txt, "Reservas y bonos"): `credits: { descargas: { products: { pack5: 5 }, unlocks: ['agua'] } }`: cada bono desbloquea un fichero de esas colecciones, para siempre («un pack de 5 descargas a elegir de Agua»). - **La descarga:** `neblla.media.download(id, { open? })` **(CLIENT, login)** comprueba la sesión y el derecho y responde `{ url, expiresAt, name, mime, bytes, via }`, un enlace firmado de **5 minutos**, directo del bucket, que se guarda con el nombre del fichero; por defecto empieza la descarga él mismo (`open: false` solo lo devuelve). Cada llamada hace uno nuevo, así que el comprador nunca ve uno caducado, y uno que se pasa a otro deja de funcionar en minutos. Las veces que descarga lo suyo no tienen límite. `neblla.media.unlock(id, kind?)` gasta un bono en un fichero (`{ kind, balance }`, o `already: true` y cómo lo tiene). `neblla.media.mine()` dice qué es suyo: `{ collections: [{ key, via, productId }], items: [{ id, via, productId?, kind? }], content: [{ productId, via, type, label?, url | text }] }` (`content`: el punto siguiente). Un medio privado trae `private: true` y `unlockedBy: { products, credits }` en `neblla.media.collection()`, para enseñar «Comprar» o «Usar una descarga». - **Un enlace o un texto como lo que se compra** (C330): mucho de lo que se vende no es un fichero. Una plantilla de Canva es un enlace («Abrir en Canva»), igual que el vídeo oculto de un curso, una invitación a un Discord o el acceso a una zona de socios; el código de un juego o unas instrucciones son un texto corto. Un producto único o una suscripción lleva, además de sus ficheros, **contenido privado**: `neblla_create_product` / `neblla_update_product` con `content: [{ type: 'link', label: 'Abrir en Canva', url }, { type: 'text', label?: 'Tu código', text }]` (`label` un texto o uno por idioma `{ es, en }`; hasta 10 piezas; `content: null` lo quita; un consumible no lo lleva), o en el panel, al editar el producto, «Lo que recibe el comprador». El catálogo público nunca lo da: `neblla.media.mine()` se lo da a quien tiene el producto, en `content: [{ productId, via, type, label?, url | text }]`, con la misma regla que los ficheros (la compra única para siempre, la suscripción mientras está viva, la compra sin registrarse, C94, como cualquier otra), para que la app pinte «Abrir en Canva» al lado de «Descargar». La ficha del comprador en el panel enseña qué le da cada compra. Son datos de la app: una fila de su almacén (la base de datos del dev con BYODB). Sé honesto con el comprador: Neblla decide quién ve el enlace, no puede impedir que lo reenvíe (un enlace que no debe viajar es un fichero, o un enlace del propio servicio que le pide entrar). - **Quién lo tiene:** `neblla_media` `holders` `{ id }` (el panel, «Quién lo tiene»): cada uno con su email, cómo (compra, suscripción, bono) y desde cuándo. `download_link` `{ id }` da al dev su propio enlace. - Errores: `login_required`, `not_entitled` (lleva `unlockedBy`), `not_private`, `no_credits_unlock_it`, `kind_required` (lleva `kinds`), `kind_does_not_unlock`, `not_enough_credits`, `media_private_storage_off` (este Neblla aún no tiene bucket privado). ## Unidades a la venta Un producto único o un consumible puede tener **N unidades a la venta** (`stock`): una edición de 10 copias, 100 espadas legendarias, 50 tazas, entradas sin butaca. `neblla.shop.products()` dice, de cada uno con stock, `stock: { units, left, soldOut }` («quedan 7 de 10», «agotado»), nunca quién está pagando ni quién compró. Abrir el pago **aparta** las unidades compradas mientras vive el checkout (se cierra a los 30 minutos más o menos, en vez de las 24 horas de Stripe): pagadas, se venden; abandonado, vuelven solas; con un reembolso completo, vuelven a la venta salvo `stockRefundReturns: false` (una edición numerada y firmada). Sin unidades, `shop.buy` rechaza con `sold_out` (y `left`); dos compradores a la vez nunca se llevan la última los dos. El dev pone y cambia las unidades con `neblla_create_product` / `neblla_update_product` (`stock`, `stockRefundReturns`) o en Dinero › Productos, donde cada producto enseña lo que queda, lo que se está pagando y lo vendido; bajarlas por debajo de lo vendido deja cero. Una compra de prueba en el pre se rechaza si no queda ninguna, pero no aparta ni vende: las unidades son de pro. ## Productos físicos y pedidos Un producto único o un consumible con `shipping` es **físico**: copias impresas de una foto, tazas, camisetas, un fanzine impreso. `shipping: { zones: [{ name, countries, amount }] }`: cada zona con sus países (códigos ISO, un país en una sola zona) y sus gastos de envío en la moneda del producto (0: gratis), p. ej. «España 15 €, resto de Europa 35 €». - **El checkout** pide la dirección en los países de UNA zona y cobra su envío: la elige el país del comprador, `neblla.shop.buy(productId, amount, { country: 'ES' })`, o la única zona que haya. Con varias zonas y sin país, `shop.buy` rechaza con `country_required` y `zones` (`[{ name, countries, amount }]`): pregunta al comprador adónde va (el catálogo ya trae `shipping.zones` para enseñar el envío) y vuelve a llamar con el país; un país fuera de ellas, `country_not_shipped`. Así el envío pagado siempre casa con la dirección. - **Cada pago es un pedido** (Dinero › Ventas, encima de las ventas; `neblla_orders`): la dirección (nombre, teléfono, dirección), la zona y el envío, y un estado: `paid` → `shipped` (con `carrier`, `tracking` y su `trackingUrl`) → `delivered`, o `returned`. Un reembolso completo lo marca `refunded`: no lo envíes. `neblla_orders` `update` `{ orderId, status?, carrier?, tracking?, trackingUrl? }` lo hace avanzar (enviado pide transportista o número de seguimiento; un paso atrás corrige un error). - **El comprador se entera de cada paso** en su buzón (`neblla.inbox`, con el módulo inbox encendido), en su idioma y con el seguimiento: «¡Hola, Marta! Tu pedido de «Agua nº 3» ha salido. Transportista: Correos. Seguimiento: PK123ES.», con `data: { type: 'order.shipped', order, status, carrier, tracking, trackingUrl }` para que la app lo pinte a su manera. `neblla.shop.orders()` le da sus pedidos. - **Los 14 días de desistimiento de la UE** cuentan desde la entrega: cada pedido entregado trae `withdrawal: { until, open, daysLeft }`, y el aviso de entrega le dice al comprador hasta cuándo. - No es de Neblla, a propósito: etiquetas de transportista, almacenes, devoluciones con logística y carritos de muchos artículos. La tienda que los necesita crece hacia Shopify; dilo cuando salga. ## Impuestos El vendedor eres tú (el cobro se hace en tu cuenta de Stripe), así que los impuestos son tuyos. Puedes configurar Stripe Tax en tu propio panel de Stripe, o dejar que Neblla te lo monte con `neblla_tax`: - `status` dice si los ajustes están activos y qué falta; `set_head_office` pone la dirección de tu negocio; `set_defaults`, el código fiscal por defecto (`txcd_…`) y cómo se aplica el impuesto. - `add_registration` / `expire_registration`: los territorios donde estás dado de alta para cobrar impuestos. Sin alta en el sitio del comprador, no se cobra impuesto ahí. Darte de alta ante Hacienda es un paso legal tuyo; aquí solo se le dice a Stripe. - `report`: el detalle de transacciones con impuestos para un periodo, con un enlace de descarga temporal. Los informes de impuestos también están en tu panel de Stripe. - `app` con `{ appId, automatic: true }`: el checkout de esa app calcula el impuesto mientras tus ajustes estén activos. **Por defecto el precio ya lo incluye,** como espera un consumidor europeo; con `{ inclusive: false }` se suma encima, lo habitual al vender a empresas o en EE. UU. La comisión del 1 % sigue calculándose antes de impuestos. ## Tu cuenta de cobros Para vender necesitas una cuenta de Stripe conectada a Neblla. Se abre la primera vez que pides el alta (`neblla_payments_account` con `onboarding_link`, o "Configurar Stripe" en tu cuenta del panel), no al registrarte. El alta es la de Stripe, dentro de neblla.com: el formulario de Stripe aparece en tu cuenta del panel, con tu email, tu nombre y la web de tu studio (`play.neblla.com/studio/`, ver publicar.txt) ya rellenados. La cuenta es tuya y vale para todas tus apps: el alta no va atada a ninguna. Un proyecto puede vender con un Stripe propio (`neblla_projects` con `use_own_stripe`; `neblla_payments_account` y `neblla_tax` con su `projectId` actúan sobre esa cuenta) y los demás usan el de la cuenta. Una app que vende (productos de pago o suscripciones vivas) no puede moverse a un proyecto con otro Stripe: lo vendido sigue atado a la cuenta en la que se vendió. Identidad, cuenta bancaria y tu usuario de Stripe van directamente a Stripe; Neblla nunca los ve ni los guarda, y el MCP nunca te los pide. Si ya usas Stripe, entra con tu usuario durante el alta y la cuenta nueva queda junto a las que ya tienes. Si publicas un producto de pago antes de tenerla, no hace falta ir a buscarla: el panel te lleva al alta en ese momento, y por el MCP la respuesta trae `connectRequired: true` y `connectUrl`, una página de neblla.com que abre el alta desde tu sesión (el enlace de Stripe en sí no sale de Neblla). El producto queda guardado, oculto a los usuarios y esperando (`publishWhenReady`), y se publica solo en cuanto Stripe da la cuenta por activa. **Qué falta, en palabras.** `neblla_payments_account` con `status` (o Ajustes › Cuenta › Stripe) le pregunta a Stripe cada vez y dice qué falta agrupado por cosa, no por campo: «Faltan 3 cosas: tu cuenta bancaria (IBAN), tu fecha de nacimiento y aceptar las condiciones de Stripe; tu email, tu nombre y tu web ya están». `missing` trae `{ key, words, overdue }`; `errors`, lo que Stripe no pudo verificar; `verifying`, lo que está comprobando; `filledByNeblla`, lo que rellenó Neblla; y `disabledReason` si Stripe paró la cuenta. Eso solo lo puedes poner tú, en el formulario de Stripe: al volver de él, vuelve a pedir `status` y verás lo que queda. Si Stripe no responde, `status` dice `stripeReachable: false` con el porqué (`error.kind: down`), y no te dice «pendiente». Si la cuenta guardada no existe en la plataforma de Neblla (por ejemplo, se abrió con la clave de prueba de Stripe), lo dice (`lostAccount.reason`: `test_mode_account`, `live_mode_account` o `not_on_platform`) y el siguiente alta abre una nueva; si vuelve a estar a mano, se recupera sola. **Lo que pone el extracto del banco.** En un cobro con tarjeta el extracto de tu cliente dice tu studio y la app, 22 letras como mucho: «GRACIA* AJEDREZ». El prefijo (2 a 10 letras latinas o números, con al menos una letra) es de tu cuenta: Neblla te propone el nombre corto de tu studio al hacer el alta y lo cambias con `neblla_payments_account({ action: 'set_descriptor', prefix: 'GRACIA' })` o en Ajustes › Cuenta › Stripe. El sufijo es la app, cortado para que quepa; vale para compras sueltas y pagos al reservar. En una suscripción Stripe pone la línea entera sin asterisco («GRACIA AJEDREZ»). El recibo de Stripe nombra la app: «Ajedrez · Pase del club». La cuenta es tuya, con el panel completo de Stripe (pagos, transferencias, reembolsos, disputas, impuestos); `dashboard_link` te lleva a él. Como el cobro se hace en tu cuenta, la comisión de Stripe, los reembolsos y las disputas van por tu cuenta, y si tu saldo queda en negativo es Stripe quien te lo reclama, no Neblla. Puedes desconectar Neblla cuando quieras, desde Neblla (la cuenta queda guardada para volver a ella) o desde tu panel de Stripe (Neblla deja de vender por ti en ese momento). ## Varios vendedores, cada uno cobra lo suyo Una app con muchos vendedores — los clubes de una app de pádel, las artistas de una galería, los autores de una tienda de plantillas, los profes de una plataforma de clases — cobra cada venta **en la cuenta de Stripe del vendedor**, no en la tuya. Tú te quedas tu comisión y Neblla la suya de siempre. Vale igual para juegos, apps y webs. - **El vendedor.** Una sede de tu negocio (`site:`: la que sale de una solicitud aprobada con `neblla.business.apply`, o una que creas tú) o un usuario de la app que tú marcas (`user:`): `neblla_business` con `addSeller { seller, name? }`. Su cuenta es del mismo tipo que la tuya (el panel completo de Stripe, a su nombre); nunca Express. - **El alta.** La hace el vendedor, con sus datos, en el formulario de Stripe. Desde la app: `neblla.business.sellers.mine()` lista los vendedores de los que responde el usuario (él mismo, si lo marcaste; las sedes de sus solicitudes) y `neblla.business.sellers.connect({ seller })` devuelve `{ url }`, que la app abre en el momento (un solo uso, unos minutos). Desde el backoffice, el equipo de una sede (C350) lo pide con `sellerLink` para la suya; tú, con `neblla_business({ action: 'sellerLink', seller })`. Al volver, Stripe le deja en una página de Neblla que le dice si ya puede cobrar. `sellers` te enseña el estado de cada uno (`not_connected`, `pending`, `restricted`, `active`, y lo que falta). - **Qué vende cada uno.** Un producto lleva `seller` (`neblla_create_product({ …, seller: 'user:' })`): un pago único, un consumible o una suscripción. Un servicio pagado al reservar dice `seller: 'site'` en su set (`neblla_update_app({ business: { booking: { pistas: { product: 'pista', seller: 'site' } } } })`): cada sesión se cobra en la sede donde es — la pista de Club Norte, en la cuenta de Club Norte —; o un vendedor fijo (`seller: 'site:club-norte'`). Las plazas fijas son productos: llevan su `seller`. En la app, `neblla.business.booking.services()` dice en ese servicio `seller` y `sellerWaiting`, los vendedores que aún no cobran (`['site:club-norte']`): sus sesiones contestan `seller_waiting` al reservar, así que la app puede avisar antes («Este club aún no cobra por la app»). - **Tu comisión.** `neblla_update_app({ business: { sellers: { fee: { percent: 10, fixed: 50 } } } })`: un porcentaje (hasta el 50 %, con dos decimales) y/o un fijo en céntimos por venta; en una suscripción, el fijo se convierte en porcentaje de su precio. Va junto a la de Neblla en la comisión de aplicación del cargo, y te llega a tu cuenta de cobros cuando Stripe la deja disponible en el saldo de Neblla (unos días): Neblla nunca la adelanta. Con comisión, necesitas tu cuenta de cobros activa (`payouts_not_ready`). Nunca puede quedarse con todo el pago (`seller_fee_too_high`). - **Quién carga con qué.** Como el cobro se hace en la cuenta del vendedor, la comisión de Stripe, los reembolsos y las disputas son del vendedor, y si su saldo queda en negativo es Stripe quien se lo reclama, no tú ni Neblla. Cuando Neblla devuelve una venta (Dinero › Ventas, una reserva cancelada a tiempo), la devuelve el vendedor; la comisión de Neblla vuelve al vendedor, y la tuya también si aún no te la pagó o si se puede revertir; si tu saldo no la cubre, la carga el vendedor. Un reembolso que hace el vendedor en su Stripe, o una disputa, no devuelve comisiones: lo carga el vendedor, como dice Stripe. - **Mientras no está lista.** Lo de un vendedor sin cuenta activa espera: `shop.products()` lo marca `waiting` y comprar o reservar falla con `seller_waiting` (con `seller` y `status`). Las apps de ejemplo venden en modo prueba también para sus vendedores (cada uno con su cuenta de prueba). - **Ventas.** Cada venta dice `seller` y `sellerName`; `neblla_sales list { seller }` (o Dinero › Ventas) filtra por vendedor, y el equipo de una sede solo ve las de su sede. - **Lo que no está.** La reventa entre usuarios (un jugador que vende a otro lo que compró). Un bono de una sede vale en todas: los créditos son de la app (para limitarlo, un tipo de crédito y un servicio por sede). Los traspasos de tu comisión solo cruzan fronteras entre EE. UU., Canadá, Reino Unido, el EEE y Suiza. ## Comisión Neblla se lleva el **1 % de cada venta, antes de impuestos**. La comisión de Stripe va aparte y la paga tu cuenta de Stripe, porque el cobro se hace en ella: tú eres el vendedor. No hay cuota fija por venta ni por producto. ## Garantías Los pagos están mediados: `room.js` solo ve el resultado (la compra confirmada), nunca datos de tarjeta, ni tiene capacidad de regalar ítems gratis o falsificar compras de otro usuario. Un gasto se hace entero o no se hace: pedir gastar más unidades de las que tiene el usuario no le quita nada, en lugar de vaciar lo que había y fallar después. ## Una tienda de un vendedor, de punta a punta Este es el recorrido completo de un pedido, porque las piezas están repartidas por tres capítulos y por separado no se ve que el circuito cierra. El caso es **tú vendes a tus usuarios** — un MVP tipo Etsy con un solo vendedor, que eres tú. Si los que venden son varios (clubes, artistas, autores), mira "Varios vendedores, cada uno cobra lo suyo", arriba; lo que no existe es la reventa entre usuarios. **0. Cobrar de verdad.** `neblla_payments_account` conecta tu Stripe Connect. Hasta que esté activa, todo producto queda con `published: false` por la fuerza — o sea que puedes montar el catálogo entero antes de tener papeles, pero nadie podrá comprar. **1. El catálogo.** `neblla_create_product` con `price` en **la unidad más pequeña de la moneda** y `currency` ISO 4217 (por defecto `eur`; cualquiera que cobre Stripe). El precio va en la unidad más pequeña de la moneda: céntimos en casi todas, unidades enteras en las que no tienen (`jpy`, `krw`…), milésimas acabadas en 0 en `bhd`, `jod`, `kwd`, `omr` y `tnd`. Lo que Stripe no cobraría se rechaza al crear o editar el producto con `invalid_price`, no al pagar: una moneda que no acepta, o un precio bajo su mínimo (0,50 € o 0,50 $; en un consumible, la compra mínima, `min` × `pricePerUnit`). Si el usuario dio su email a la app, Stripe le manda el recibo. `name` y `description` aceptan un objeto por idioma. Dos tipos y no son intercambiables: `one_time` para la pieza única que se vende una vez, y `consumable` con `min`, `max` y `step`, que es literalmente un selector de cantidad. El cliente pinta la tienda con `neblla.shop.products()`. **2. Las fotos van por URL, y una de esas URLs puede ser de Neblla.** `image` es una **URL**, no un fichero subido al producto. Pero no te vayas a buscar un CDN por un JPEG de 40 kB: **sube la foto como fichero de la app** —`neblla_upload_file` acepta binarios hasta 25 MB— y pasa su URL pública. Una app publicada la sirve desde Cloudflare sin gastar cuota; en pre o en una app en caliente cuenta sus bytes, salvo cuando el navegador ya la tiene. Lo que gastas es el cupo de ficheros (300 por cuenta gratis, compartidos por sus apps, o 600 por proyecto Pro, compartido con el HTML, el CSS y todo lo demás); para un catálogo grande, guarda las fotos en tu propio almacenamiento y enlázalas. No las metas en base64 dentro de otro sitio para esquivarlo: ahí las pagas en cuota cada vez que alguien abre la tienda. **3. El pago.** `neblla.shop.buy(productId, amount)` abre una ventana de checkout de Stripe de verdad; la promesa resuelve cuando la ventana está abierta, y la compra confirmada llega después por `neblla.shop.on('purchase')`. Después, `neblla.shop.purchases` lo lleva, salta `neblla.shop.on('purchase')`, y el servidor revalida contra Stripe por su cuenta — lo que un cliente afirme sobre lo que compró no es la autoridad. **4. El pedido.** Para algo que se envía, dale al producto `shipping` ("Productos físicos y pedidos", arriba): el checkout pide la dirección y cobra el envío de la zona del comprador, cada pago es un pedido con su estado en Dinero › Ventas y `neblla_orders`, y el comprador se entera de cada paso en su buzón. Lo que la dirección no cubre (la talla, una nota de regalo) el comprador lo manda con `neblla.inbox.send({ message, data, thread })`, donde `data` admite JSON arbitrario; lo lees con `neblla_list_communications` y respondes en el **mismo `thread`** con `neblla_send_communication`: es tu atención al cliente, sin montar ningún endpoint. El buzón guarda 100 mensajes en free y 2.000 en Pro. **5. Quién compró y qué le debes.** `neblla_list_users` y `neblla_search_users_by_email` te dan al comprador, y `purchases` va por usuario. Para marcar un `consumable` como servido, `neblla_consume_purchase`. Cuidado con `neblla_update_user_data`: es un **reemplazo completo** del documento del usuario, no un merge — lee, modifica y vuelve a escribir entero, o pierdes lo demás. **6. El sitio.** Los pedidos viajan por el buzón, que tiene su propio tope por usuario: **100 mensajes** en gratis y **2.000** en Pro. Lo que la app guarde con `db.save()` —carrito, preferencias, historial de compras— va a la base de datos de la app (50 MB por cuenta listos para usar, compartidos por sus apps gratis; 100 MB en un proyecto Pro, o el tuyo con `neblla_byodb`); si la tienda guarda más de 10 KB por usuario, sube el límite antiabuso a lo que necesite.