propuesta · ago 2026

Pasarela de pagos · finanzas

El 3% que hoy pagamos en silencio.

Cada cobro con tarjeta deja ~2.9% + 30¢ en Stripe y hoy Mogos lo absorbe sin que nadie lo vea: el fee real se guarda en Transaction.feeAmount y ningún reporte lo lee. El campo Comisión del admin existe desde v1, viaja hasta el portal — y nadie lo usa. Esta propuesta lo enciende como manda la industria: desglose visible antes de cobrar, línea separada en el recibo, y una línea contable de primera clase que hace que los libros cierren al centavo. La conciliación no se toca.

Modelo
Línea PAYMENT_SURCHARGE
El %
PaymentMethod.commission
Conciliación
Sin cambios
Tolerancias nuevas
Cero
Estándar
Desglose + tope 3%
01La comisión que nadie lee
payment_methods.commission

En el admin, cada método de pago tiene un campo Comisión con su porcentaje. El portal lo recibe del API en cada carga (use-payment-methods.ts)… y ahí muere: ningún componente lo muestra, ninguna pantalla lo suma, ningún servicio lo cobra. Del otro lado, Stripe sí cobra el suyo: el fee real queda registrado en Transaction.feeAmount con el neto en netAmountescritos en cada pago, leídos por nadie. Resultado: la plataforma sabe exactamente cuánto pierde por cobrar con tarjeta, y lo pierde igual.

Método de pago · Stripe

admin · hoy
Stripe
3%

El 3% está configurado desde la migración de v1. Se guarda, se lista, se ordena por él en la tabla… y no participa en ningún cobro. Es el interruptor que esta propuesta enciende — sin inventar campos nuevos.

Se muestra en el portalNo
Se suma al cobroNo
Aparece en el reciboNo

El interruptor dormido. `PaymentMethod.commission` existe, se administra y viaja al portal en cada carga. La propuesta no agrega un campo: le da significado al que ya está.

Lo que enseñó v1 — y lo que no se repite

v1 sí cobraba el recargo, pero al confirmar mutaba el total de la orden (total += fee), con un fallback de 8% hardcodeado en el backend y un 3% hardcodeado aparte en el frontend. La deuda del cliente crecía después de pagar y el precio del servicio dejaba de ser el precio del servicio.

v2 eliminó esa familia de mutaciones a propósito: el balance se calcula en un solo servicio, y la conciliación bancaria exige igualdad al centavo exacto por trigger de base de datos. El recargo vuelve — pero como línea con nombre, no como total que cambia solo.

02La regla del centavo
3 capas · match exacto

El sistema financiero de v2 es de tres capas: lo que el cliente pagó (A), lo que el proveedor liquidó (B) y lo que llegó al banco (C). Con el recargo como línea, un pago de $500 por Stripe al 3% viaja así — y cada capa cierra sola, sin tolerancias nuevas:

Capa ACliente · cotización
Deuda del flete$500.00
+ línea Fee de servicio 3%+$15.00
Pago registrado (bruto)$515.00
515 pagado = 515 adeudado
Capa BStripe · payout
Bruto cobrado$515.00
Fee real de Stripe−$15.24
Neto liquidado$499.76
El payout absorbe el recargo solo
Capa CBanco · conciliación
Crédito bancario$499.76
Match contra payoutexacto
Tolerancia aplicada$0.00
Trigger de centavo: intacto

Por qué B y C no se tocan. La liquidación ya maneja bruto/fee/neto arbitrarios: el batch suma lo que Stripe reporte y el banco se concilia contra el neto. Lo único que faltaba era poder decir, en la Capa A, cuánto del bruto era recargo.

03Una línea de primera clase
LineItemTypeEnum.PAYMENT_SURCHARGE

El recargo nace como línea de cotización con tipo propio, dentro de la misma transacción de base de datos que ya crea Transaction + Payment + Receipt — atómico, idempotente y anulable como una sola unidad. Hay precedente directo: la migración v1→v2 ya importó los recargos históricos como línea «Recargo por método de pago». Lo que faltaba era el tipo con nombre.

El tipo
PAYMENT_SURCHARGE
Nuevo valor del enum de líneas — no el genérico EXTRA_CHARGE. Dos razones duras: el rebuild de tarifas de fletes borra y reconstruye las líneas EXTRA_CHARGE (el recargo se esfumaría al editar una tarifa), y sin tipo propio el recargo se reportaría como ingreso de flete, invisible como negocio.
El porcentaje
PaymentMethod.commission
El campo que ya existe. 0 = sin recargo — Zelle, efectivo y transferencia quedan en 0 y su flujo no cambia en nada. Stripe arranca en 3%; PayPal y Binance se deciden por método, desde el admin, sin tocar código.
Dónde nace
createPaymentRecord
En el único escritor de pagos de pasarela, dentro del $transaction existente y detrás del mismo guard de idempotencia (confirm del cliente y webhook de Stripe compiten hoy; gana uno solo, y la línea viaja con el ganador). Total y pagado crecen por el mismo monto: el rollup cierra exacto, sin tolerancias nuevas.
El cobro
createPaymentIntent
Stripe cobra el bruto (base × (1 + commission)) y el intent guarda baseAmount + surchargeAmount en metadata para trazabilidad. La verificación server-side valida el desglose contra el método — el cliente nunca manda el porcentaje.
El ingreso
INCOME_GATEWAY_SURCHARGE
Nueva categoría en el export contable (QBO), separada del ingreso de fletes. Por primera vez el spread es visible: recargo cobrado (ingreso) contra FEES_PROCESSOR (gasto). Hoy ese neto es invisible y siempre negativo.
Estándar de industria · compliance

Así lo hacen QuickBooks, Bill.com y el invoicing de Stripe, y así lo exigen las redes: desglose visible antes de pagar, recargo como línea separada en el recibo, tope de 3% (regla Visa/Mastercard en EE.UU.) y nunca por encima del costo real de aceptación. El diseño lo respeta de fábrica: el % vive por método, es visible en cada pantalla, y se apaga poniendo commission = 0. Contablemente es GAAP puro: recargo = ingreso propio, fee del procesador = gasto — jamás neteados en silencio.

04Cero sorpresas en el portal
apps/payments-portal

La regla de oro es que el cliente lo vea antes, no después. De cara al cliente la etiqueta es «Fee de servicio Mogos» — profesional y genérica, como los service fees de cualquier marketplace; «recargo» queda como concepto interno y contable. Aparece dos veces: como dato del método (al elegir cómo pagar) y como línea del desglose (antes de confirmar). El botón dice la cifra final — la misma que saldrá en el estado de cuenta.

elegir método · el dato

El fee es un dato del método, no un castigo. Cada opción declara el suyo en tipografía tabular; los métodos sin fee lo dicen en verde — el incentivo a Zelle queda implícito, sin regañar a nadie.

confirmar · el desglose

El botón dice la verdad completa. Abono y recargo en líneas separadas, el total en la placa de marca, y el CTA con la cifra exacta del cargo. Nada que descubrir después.

05El recibo y el viaje del dinero
dos líneas · un cargo

Después de pagar, el desglose no desaparece: la pantalla de éxito — y el comprobante — repiten las dos líneas. El abono amortiza la deuda del flete; el recargo queda como línea con nombre. Y de ahí en adelante el dinero viaja por las tuberías que ya existen.

Dos líneas también en el comprobante. El recibo hereda el desglose: quien audite un pago ve el abono y el recargo por separado — exactamente lo que exigen las reglas de las redes de tarjetas.

06Los bordes, resueltos de fábrica
void · refund · parciales

Un recargo mal anulado deja una deuda fantasma; un recargo duplicado por un webhook repetido infla la cotización. Cada caso delicado tiene una regla — decidida ahora, no descubierta en producción.

Anulación de pago

El void desactiva el pago y anula su línea de recargo en la misma transacción. Total y pagado bajan juntos por el mismo monto: el balance vuelve solo a donde estaba. Sin línea huérfana, sin deuda fantasma.

Reembolso y disputa

Reembolso total: se devuelve el bruto ($515) y la línea de recargo se anula con él. Reembolso parcial: se devuelve lo reembolsado y la línea no se toca — el servicio de procesamiento ya se prestó. La misma regla aplica a disputas.

Pagos parciales

El recargo se calcula sobre lo que se paga, no sobre la deuda. Tres abonos por Stripe traen tres líneas, cada una atada a su pago. Quien mezcla métodos paga recargo solo por la parte que pasó por tarjeta.

Idempotencia

El confirm del cliente y el webhook de Stripe ya compiten hoy y gana uno solo (índice único por intent). La línea nace dentro de ese mismo guard: webhook repetido, doble clic o reintento — una sola línea, siempre.

Rebuild de tarifas

Las líneas EXTRA_CHARGE se borran y reconstruyen cuando se edita una tarifa de flete. PAYMENT_SURCHARGE queda fuera de esa lista: editar una tarifa jamás toca un recargo ya cobrado.

Validación server-side

El porcentaje nunca viene del cliente: el server lo lee del método al crear el intent y lo re-valida al confirmar. Un portal viejo o un request manipulado no pueden cobrar un recargo distinto al configurado.

07El spread, por fin visible
ingreso vs gasto

Con el recargo como categoría propia de ingreso, finanzas ve por primera vez las dos columnas juntas: lo cobrado por recargo contra el fee real que Stripe descontó. A 3% el recargo cubre ~98% del costo — el fee corre sobre el bruto y suma 30¢ fijos por cobro — y el tope de las redes impide subirlo más. La diferencia deja de ser una pérdida invisible y pasa a ser un número chico, conocido y decidido.

Pasarela · agosto 2026

finanzas · propuesta
Cobros con tarjeta41 pagos · $42,710.00
Recargo cobrado · INCOME_GATEWAY_SURCHARGE+$1,244.00
Fee real de Stripe · FEES_PROCESSOR−$1,265.90
Spread del mes−$21.90

Hoy ese −$21.90 es −$1,265.90: sin recargo, el fee completo sale del margen y ningún reporte lo muestra. Con la categoría propia, gerencia decide con el número en la mano — y el 98% del costo lo paga quien elige la tarjeta.

Sin tocar la conciliación. El panel de payouts (Bruto / Comisión / Neto) sigue idéntico: esto es una vista de reporting sobre categorías nuevas, no un cambio en el motor de matching.

08Lo que no se toca
la garantía de calidad

La instrucción fue clara: no afectar la calidad del sistema de pagos. Esta es la lista explícita de lo que la propuesta deja exactamente como está — y es la lista que un QA puede verificar punto por punto.

  • El rollup del balance — un solo servicio, tolerancia de $0.50 solo para el estado, montos siempre reales. Igual que hoy.
  • La conciliación bancaria — trigger de centavo exacto, matching por neto de payout. Cero cambios en Capas B y C.
  • Transaction.feeAmount / netAmount — el fee real de Stripe se sigue registrando igual. El recargo no lo reemplaza: son ingreso y gasto, cada uno en su columna.
  • Los métodos sin recargo — Zelle, efectivo y transferencia tienen commission = 0: su flujo, sus pantallas y sus recibos no cambian ni un píxel.
  • La idempotencia de pasarela — el índice único por intent y el guard confirm-vs-webhook quedan intactos; la línea solo se suma al ganador.
  • El total del servicio — el precio del flete nunca muta. El recargo es una línea aparte, visible, anulable y auditable. Lo de v1 no vuelve.

El recargo entra por la puerta grande: una línea con nombre, no un total que cambia solo.

Un enum nuevo, un campo que ya existía y las tuberías de siempre. El cliente ve el desglose antes de pagar, el recibo lo repite, el flete nunca cambia de precio y la conciliación sigue cerrando al centavo. Y por primera vez, el costo de cobrar con tarjeta es un número que alguien decidió — no uno que nadie vio.