@andresh11/pay-preview
v0.1.0
Published
Framework-agnostic, tree-shakeable payment UI preview components (Card, Nequi, PSE, Bancolombia). One .lite.tsx source, multi-target (React, Vue, Svelte, Solid, RNative).
Downloads
75
Maintainers
Readme
@andresh11/pay-preview
Componentes de UI agnósticos y tree-shakeables para previsualización de pagos — tarjeta, Nequi, PSE y Bancolombia. Un solo source
.lite.tsx, multi-target (React, Vue, Svelte, Solid, React Native). Sin dependencias de runtime.
Framework-agnostic, tree-shakeable payment UI preview components — card, Nequi, PSE, Bancolombia. One source
.lite.tsx, multi-target (React, Vue, Svelte, Solid, React Native). Zero runtime dependencies.
📑 Tabla de contenidos / Table of contents
Español
¿Qué es?
Una librería UI de previsualización de pagos para Colombia. No procesa pagos, no llama a APIs, no almacena datos: solo renderiza componentes de UI que muestran cómo se verá la información de pago (tarjeta, Nequi, PSE, Bancolombia) antes de que el usuario confirme.
Está pensada para stayspot, kiddoFamily, kiddoAdmin y cualquier otro frontend que necesite mostrar vistas previas ricas de medios de pago.
Características
- 🎨 Multi-framework: una sola fuente compila a 5 targets idiomáticos (React, Vue, Svelte, Solid, React Native).
- 🌳 Tree-shakeable real: importás solo lo que usás. CreditCard solo en React ≈ 11 KB / 4 KB gz.
- 🚫 Cero dependencias runtime: tu bundle no se infla con nada que no
uses. Solo
react/react-domcomo peer si importás de/react. - 🎯 Strategy pattern: validación pura (Luhn, BIN, longitud) extensible para nuevos métodos.
- 🔒 TypeScript estricto: tipos públicos exportados en cada subpath.
- 🎨 CSS con variables: personalizá colores, gradientes, glow sin tocar la librería.
- 📦 Componentes 100% agnósticos: mismo
.lite.tsx→ 5 frameworks. - 🎯 Auto-flip 3D: la tarjeta rota al reverso cuando recibe foco el CVC.
- 🎯 Detección automática de marca: Visa, Mastercard, Amex, genérica.
Galería visual
Todas las previews en estado funcional con sus datos por defecto:
Tarjeta de crédito/débito — CreditCard
| Marca | Preview |
|---|---|
| Visa |
|
| Mastercard |
|
| Amex |
|
| Genérica |
|
| Reverso (auto-flip con CVC) |
|
Billetera y transferencias — Nequi / PSE / Bancolombia
| Método | Preview |
|---|---|
| NequiPreview |
|
| PSEPreview |
|
| BancolombiaTransferPreview |
|
💡 Las previews son reactivas — actualizá los campos del formulario y la previsualización cambia en vivo (esto es lo que muestra el demo).
Instalación
npm install @andresh11/pay-preview react react-domreact y react-dom son peer dependencies (no se incluyen en el bundle).
Si usás Vue, Svelte o Solid, no necesitás React. Cada target tiene su propio peer (o ninguno, según el framework).
Uso rápido
React (TypeScript)
import { CreditCard } from '@andresh11/pay-preview/react'
import '@andresh11/pay-preview/styles/credit-card.css'
export const PaymentScreen = () => (
<CreditCard
number="4111 1111 1111 1111"
cardHolder="Andres Hernandez"
expMonth="12"
expYear="28"
cvc="123"
/>
)Vue 3
<script setup>
import { CreditCard } from '@andresh11/pay-preview/vue'
import '@andresh11/pay-preview/styles/credit-card.css'
</script>
<template>
<CreditCard
number="4111 1111 1111 1111"
cardHolder="Andres H"
expMonth="12"
expYear="28"
cvc="123"
/>
</template>Svelte
<script>
import { CreditCard } from '@andresh11/pay-preview/svelte'
import '@andresh11/pay-preview/styles/credit-card.css'
</script>
<CreditCard
number="4111 1111 1111 1111"
cardHolder="Andres H"
expMonth="12"
expYear="28"
cvc="123"
/>Solid
import { CreditCard } from '@andresh11/pay-preview/solid'
import '@andresh11/pay-preview/styles/credit-card.css'
export const PaymentScreen = () => (
<CreditCard
number="4111 1111 1111 1111"
cardHolder="Andres H"
expMonth="12"
expYear="28"
cvc="123"
/>
)Vue, Svelte y Solid requieren bundler con los plugins oficiales para procesar los SFC. Vite los trae por defecto. Si usás webpack o Rollup necesitás el plugin correspondiente.
Componentes disponibles
| Componente | Descripción | Tipo de preview |
|---|---|---|
| CreditCard | Tarjeta Visa/Mastercard/Amex/genérica con flip 3D | Tarjeta física |
| PaymentCatalog | Lista radio accesible de métodos | Selector |
| NequiPreview | Número de celular Nequi | Billetera digital |
| PSEPreview | Banco + datos del pagador | Débito ACH |
| BancolombiaTransferPreview | Cuenta + tipo + titular | Transferencia |
Ver las screenshots en la galería visual para ver cada uno en acción.
Tipos de marcas (CardType)
type CardType = 'visa' | 'mastercard' | 'amex' | 'generic'Si no se pasa brand, CreditCard lo autodetecta por el BIN del número.
Referencia de API
CreditCard
interface ICreditCardProps {
number: string // número de tarjeta (con o sin separadores)
cardHolder: string // nombre del titular tal como aparece en la tarjeta
expMonth: string // mes (1-12) o "01"-"12"
expYear: string // año ("28" o "2028")
cvc: string // código de seguridad
isFlipped?: boolean // forzar reverso (default: heurística por cvc)
brand?: CardType // forzar marca (default: autodetección)
genericBrandLabel?: string // texto cuando brand === 'generic' (default: "STAYSPOT")
}Auto-flip
El reverso de la tarjeta (donde aparece el CVC) se muestra automáticamente cuando se tipea el CVC. Si querés forzar el flip manualmente:
const [showBack, setShowBack] = useState(false)
<CreditCard ... cvc="123" isFlipped={showBack} />Si pasás isFlipped, esa gana sobre la heurística.

Marca automática
// autodetección (Visa si empieza en 4, Mastercard 51-55, Amex 34/37, resto genérica)
<CreditCard number="4111..." ... />
// forzar marca (útil para BINs desconocidos)
<CreditCard brand="generic" number="..." genericBrandLabel="Mi Tienda" ... />NequiPreview
interface INequiPreviewProps {
phone: string // celular Nequi (10 dígitos, solo dígitos)
genericBrandLabel?: string
}Muestra el número con formato 300 123 4567 y un glow animado corporativo.
PSEPreview
interface IPSEPreviewProps {
bankName?: string // nombre del banco
userFullName?: string // nombre del pagador
userLegalId?: string // cédula
userLegalIdType?: string // tipo (CC, CE, etc.)
email?: string // email de confirmación
genericBrandLabel?: string
}BancolombiaTransferPreview
type AccountType = 'SAVINGS' | 'CHECKING' | 'AHORROS' | 'CORRIENTE'
interface IBancolombiaTransferPreviewProps {
accountNumber?: string
accountType?: AccountType // default: 'AHORROS'
holderName?: string
holderLegalId?: string
genericBrandLabel?: string
}PaymentCatalog
type PaymentMethodCode = 'CARD' | 'NEQUI' | 'PSE' | 'BANCOLOMBIA_TRANSFER'
interface IPaymentMethodOption {
code: PaymentMethodCode
label: string // ej: "Tarjeta de crédito"
description: string // ej: "Visa, Mastercard, Amex"
available: boolean // si está habilitado (gris si false)
}
interface IPaymentCatalogProps {
methods: IPaymentMethodOption[]
selected?: PaymentMethodCode
onSelect?: (code: PaymentMethodCode) => void
title?: string // título opcional arriba del listado
}const methods: IPaymentMethodOption[] = [
{ code: 'CARD', label: 'Tarjeta', description: 'Visa, MC, Amex', available: true },
{ code: 'NEQUI', label: 'Nequi', description: 'Desde tu celular', available: true },
{ code: 'PSE', label: 'PSE', description: 'Débito bancario', available: false },
]
<PaymentCatalog
methods={methods}
selected={selected}
onSelect={setSelected}
title="Métodos de pago"
/>Strategy pattern — validación pura
Si necesitás validar campos antes de mandar al backend, la librería trae un validador de tarjeta listo y extensible para otros métodos:
import {
CardPaymentStrategy,
validateAndNormalizeCard,
} from '@andresh11/pay-preview/strategies'
const [error, normalized] = validateAndNormalizeCard({
number: '4111111111111111',
cardHolder: 'Andres Hernandez',
expMonth: '12',
expYear: '28',
cvc: '123',
})
if (error) {
console.error(error) // mensaje en español
} else {
console.log(normalized)
// {
// method: 'CARD',
// number: '4111111111111111',
// numberFormatted: '4111 1111 1111 1111',
// cardHolder: 'Andres Hernandez',
// expMonth: '12',
// expYear: '28',
// cvc: '123',
// brand: 'visa',
// }
}Detectores standalone (sin Strategy)
import { detectCardBrand, isValidLuhn, hasValidLength } from '@andresh11/pay-preview/core'
detectCardBrand('4111111111111111') // 'visa'
detectCardBrand('5105105105105100') // 'mastercard'
detectCardBrand('371449635398431') // 'amex'
detectCardBrand('6011111111111117') // 'generic'
isValidLuhn('4111111111111111') // true
hasValidLength('4111111111111111', 'visa') // trueExtender con tu propio método
import type {
IPaymentMethodStrategy,
IPaymentMethodMetadata,
TPaymentMethodCode,
TValidationResult,
} from '@andresh11/pay-preview'
import { PaymentStrategiesRegistry } from '@andresh11/pay-preview/strategies'
class MyCustomStrategy implements IPaymentMethodStrategy<TMyInput, TMyOutput> {
readonly code: TPaymentMethodCode = 'DAVIPLATA' // string literal
validate(raw: TMyInput): TValidationResult {
return raw.phone.length === 10 ? null : 'Celular inválido'
}
normalize(raw: TMyInput): TMyOutput {
return { method: 'DAVIPLATA', phone: raw.phone }
}
getMetadata(): IPaymentMethodMetadata {
return {
code: 'DAVIPLATA',
label: 'Daviplata',
description: 'Billetera digital',
enabled: true,
}
}
}
const registry = new PaymentStrategiesRegistry()
registry.register(new MyCustomStrategy())Tree-shaking
| Importación | Tamaño min | Tamaño gz |
|---|---|---|
| Solo CreditCard (/react) | ~11 KB | ~4 KB |
| CreditCard + PaymentCatalog + Nequi (/react) | ~14 KB | ~5 KB |
| Root completo (@andresh11/pay-preview) — solo strategies/core | ~3 KB | ~1.5 KB |
El bundler (Vite, esbuild, Rollup) descarta lo que no importaste, incluyendo el CSS si no lo importás.
Personalización con CSS variables
Todos los estilos usan prefijo --pp- (pay-preview). Override en tu CSS
global o en un selector contenedor:
:root {
/* ── CreditCard ── */
--pp-card-width: 360px;
--pp-card-height: 210px;
--pp-card-radius: 16px;
--pp-card-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
--pp-card-flip-duration: 600ms;
/* ── Marcas ── */
--pp-brand-visa: linear-gradient(135deg, #ff6b6b, #4ecdc4);
--pp-brand-mastercard: linear-gradient(135deg, #e96443, #904e95);
--pp-brand-amex: linear-gradient(135deg, #006fcf, #00b7ff);
--pp-brand-generic: linear-gradient(135deg, #1c1d22, #5b86e5);
/* ── Nequi glow ── */
--pp-nq-glow-1: #2000a0;
--pp-nq-glow-2: #b900c5;
--pp-nq-text: #ffffff;
/* ── Bank method (PSE + Bancolombia) ── */
--pp-bm-card-bg: #ffffff;
--pp-bm-card-fg: #0f172a;
--pp-bm-accent: #4f46e5;
}Las variables usan fallback en el componente, así que podés definir solo las que necesitás.
Subpath exports
| Path | Qué exporta | Peer deps necesarias |
|---|---|---|
| @andresh11/pay-preview | Strategies + detectores + tipos | ninguna |
| @andresh11/pay-preview/react | Componentes React | react, react-dom |
| @andresh11/pay-preview/vue | Componentes Vue | vue (en tu bundler) |
| @andresh11/pay-preview/svelte | Componentes Svelte | svelte (en tu bundler) |
| @andresh11/pay-preview/solid | Componentes Solid | solid-js |
| @andresh11/pay-preview/react-native | Componentes React Native | react, react-native |
| @andresh11/pay-preview/core | Detectores standalone | ninguna |
| @andresh11/pay-preview/strategies | Strategies + validador de tarjeta | ninguna |
| @andresh11/pay-preview/styles/credit-card.css | CSS de CreditCard | ninguna |
| @andresh11/pay-preview/styles/nequi.css | CSS de NequiPreview | ninguna |
| @andresh11/pay-preview/styles/bank-method.css | CSS de PSE + Bancolombia | ninguna |
| @andresh11/pay-preview/styles/catalog.css | CSS de PaymentCatalog | ninguna |
Stack técnico
- Mitosis 0.14 — una sola fuente
.lite.tsx→ 5 frameworks. - TypeScript estricto —
strict: true,noUncheckedIndexedAccess: true. - ESLint 8 +
eslint-plugin-perfectionist— 0 warnings. - Sin runtime dependencies — solo
react/react-domcomo peer. - Cero polyfills — solo APIs nativas que ya están en navegadores modernos.
Desarrollo
npm install
npm run typecheck # tsc --noEmit
npm run lint # eslint --max-warnings 0
npm run build # mitosis → tsc → css → copy
npm run dev:demo # demo con HMR (http://localhost:5173)
npm run build:demo # bundle de demo (verificación final)
npm run validate:tree-shaking # verifica que bundle < 30 KBEstructura
src/
├── core/
│ ├── detectors/ ← detectCardBrand, isValidLuhn, hasValidLength
│ └── formatters/ ← formatCardNumber, formatExpMonth, etc.
├── mitosis/ ← fuentes .lite.tsx + CSS (generan 5 targets)
│ ├── CreditCard.lite.tsx
│ ├── NequiPreview.lite.tsx
│ ├── PSEPreview.lite.tsx
│ ├── BancolombiaTransferPreview.lite.tsx
│ ├── PaymentCatalog.lite.tsx
│ └── styles/ ← credit-card, nequi, bank-method, catalog
├── strategies/
│ ├── types/ ← IPaymentMethodStrategy, PaymentStrategiesRegistry
│ └── card/ ← CardPaymentStrategy + validateAndNormalizeCard
└── types/ ← tipos públicos purosFlujo de build (multi-target)
src/mitosis/*.lite.tsx
│
├── build-mitosis.mjs ──→ 5 targets × N componentes
│ (react, vue, svelte, solid, reactNative)
│
├── build-ts.mjs ────────→ dist/{index,core,strategies,types}/*
│
├── build-css.mjs ───────→ dist/styles/*.css (minificado)
│
└── copy-mitosis.mjs ────→ dist/{target}/* (bundle por target
+ index.d.ts autogenerado)copy-mitosis.mjs también genera un index.d.ts por target extrayendo los
export interface y export type de cada .lite.tsx, así TypeScript puede
tipar las props de los componentes sin tener que procesar JSX de Mitosis.
Notas de compatibilidad
Mitosis 0.14 + Windows
La build de Mitosis en Windows tiene 5 workarounds documentados:
--forcerompe el path parser → no usarlo; limpiarsrc/components/antes.--out-dirignora la extensión correcta con múltiples archivos: invocar mitosis archivo por archivo y capturar stdout.mitosis.config.jsoncontargets[]NO funciona en Windows.--state useState(no defaultuseLocalProxy) para emitir código React idiomático sinimport { useLocalProxy } from '@builder.io/mitosis'."use client"se emite automáticamente al usaruseStore()en React: post-procesar y eliminar antes de exponer.
ESLint 8.57
NO soporta ignoreTypeLikeDeclarations ni ignoreFunctionDeclarations
(del plugin perfectionist). Se usa ESLint 8.57 sin esas reglas.
English
What is it?
A payment UI preview library for Colombia. It does NOT process payments, does NOT call APIs, does NOT store data: it only renders UI components that show how a payment method will look (card, Nequi, PSE, Bancolombia) before the user confirms.
It's designed for stayspot, kiddoFamily, kiddoAdmin, and any other frontend that needs rich previews of payment methods.
Features
- 🎨 Multi-framework: one source compiles to 5 idiomatic targets (React, Vue, Svelte, Solid, React Native).
- 🌳 Real tree-shakeable: import only what you use. CreditCard alone in React ≈ 11 KB / 4 KB gz.
- 🚫 Zero runtime dependencies: your bundle doesn't grow with anything
you don't use. Only
react/react-domas peer if you import from/react. - 🎯 Strategy pattern: pure validation (Luhn, BIN, length) extensible for new methods.
- 🔒 Strict TypeScript: public types exported in each subpath.
- 🎨 CSS variables: customize colors, gradients, glow without touching the library.
- 📦 100% agnostic components: same
.lite.tsx→ 5 frameworks. - 🎯 3D auto-flip: the card rotates to its back when CVC receives focus.
- 🎯 Auto brand detection: Visa, Mastercard, Amex, generic.
Visual gallery
All previews in functional state with default data:
Credit/debit card — CreditCard
| Brand | Preview |
|---|---|
| Visa |
|
| Mastercard |
|
| Amex |
|
| Generic |
|
| Back (auto-flip with CVC) |
|
Wallets and transfers — Nequi / PSE / Bancolombia
| Method | Preview |
|---|---|
| NequiPreview |
|
| PSEPreview |
|
| BancolombiaTransferPreview |
|
💡 The previews are reactive — update the form fields and the preview changes live (this is what the demo shows).
Installation
npm install @andresh11/pay-preview react react-domreact and react-dom are peer dependencies (not bundled).
If you use Vue, Svelte, or Solid, you don't need React. Each target has its own peer (or none, depending on the framework).
Quick start
React (TypeScript)
import { CreditCard } from '@andresh11/pay-preview/react'
import '@andresh11/pay-preview/styles/credit-card.css'
export const PaymentScreen = () => (
<CreditCard
number="4111 1111 1111 1111"
cardHolder="Andres Hernandez"
expMonth="12"
expYear="28"
cvc="123"
/>
)Vue 3
<script setup>
import { CreditCard } from '@andresh11/pay-preview/vue'
import '@andresh11/pay-preview/styles/credit-card.css'
</script>
<template>
<CreditCard
number="4111 1111 1111 1111"
cardHolder="Andres H"
expMonth="12"
expYear="28"
cvc="123"
/>
</template>Svelte
<script>
import { CreditCard } from '@andresh11/pay-preview/svelte'
import '@andresh11/pay-preview/styles/credit-card.css'
</script>
<CreditCard
number="4111 1111 1111 1111"
cardHolder="Andres H"
expMonth="12"
expYear="28"
cvc="123"
/>Solid
import { CreditCard } from '@andresh11/pay-preview/solid'
import '@andresh11/pay-preview/styles/credit-card.css'
export const PaymentScreen = () => (
<CreditCard
number="4111 1111 1111 1111"
cardHolder="Andres H"
expMonth="12"
expYear="28"
cvc="123"
/>
)Vue, Svelte, and Solid require the bundler to process SFC files. Vite includes them by default. If you use webpack or Rollup you need the corresponding plugin.
Available components
| Component | Description | Preview type |
|---|---|---|
| CreditCard | Visa/Mastercard/Amex/generic card with 3D flip | Physical card |
| PaymentCatalog | Accessible radio list of methods | Selector |
| NequiPreview | Nequi phone number | Digital wallet |
| PSEPreview | Bank + payer details | ACH debit |
| BancolombiaTransferPreview | Account + type + holder | Transfer |
See the screenshots in the visual gallery to see each one in action.
Card brands (CardType)
type CardType = 'visa' | 'mastercard' | 'amex' | 'generic'If brand is not passed, CreditCard auto-detects by the BIN of the number.
API reference
CreditCard
interface ICreditCardProps {
number: string // card number (with or without separators)
cardHolder: string // cardholder name as shown on the card
expMonth: string // month (1-12) or "01"-"12"
expYear: string // year ("28" or "2028")
cvc: string // security code
isFlipped?: boolean // force back (default: heuristic by cvc)
brand?: CardType // force brand (default: autodetection)
genericBrandLabel?: string // text when brand === 'generic' (default: "STAYSPOT")
}Auto-flip
The back of the card (where CVC is shown) is automatically displayed when CVC is typed. If you want to force the flip manually:
const [showBack, setShowBack] = useState(false)
<CreditCard ... cvc="123" isFlipped={showBack} />If you pass isFlipped, it wins over the heuristic.

Brand auto-detection
// autodetection (Visa if starts with 4, Mastercard 51-55, Amex 34/37, else generic)
<CreditCard number="4111..." ... />
// force brand (useful for unknown BINs)
<CreditCard brand="generic" number="..." genericBrandLabel="My Store" ... />NequiPreview
interface INequiPreviewProps {
phone: string // Nequi cell number (10 digits, no separators)
genericBrandLabel?: string
}Shows the number formatted as 300 123 4567 with an animated corporate glow.
PSEPreview
interface IPSEPreviewProps {
bankName?: string // bank name
userFullName?: string // payer name
userLegalId?: string // ID number
userLegalIdType?: string // type (CC, CE, etc.)
email?: string // confirmation email
genericBrandLabel?: string
}BancolombiaTransferPreview
type AccountType = 'SAVINGS' | 'CHECKING' | 'AHORROS' | 'CORRIENTE'
interface IBancolombiaTransferPreviewProps {
accountNumber?: string
accountType?: AccountType // default: 'AHORROS'
holderName?: string
holderLegalId?: string
genericBrandLabel?: string
}PaymentCatalog
type PaymentMethodCode = 'CARD' | 'NEQUI' | 'PSE' | 'BANCOLOMBIA_TRANSFER'
interface IPaymentMethodOption {
code: PaymentMethodCode
label: string // e.g. "Credit card"
description: string // e.g. "Visa, Mastercard, Amex"
available: boolean // if enabled (greyed if false)
}
interface IPaymentCatalogProps {
methods: IPaymentMethodOption[]
selected?: PaymentMethodCode
onSelect?: (code: PaymentMethodCode) => void
title?: string // optional title above the list
}const methods: IPaymentMethodOption[] = [
{ code: 'CARD', label: 'Card', description: 'Visa, MC, Amex', available: true },
{ code: 'NEQUI', label: 'Nequi', description: 'From your phone', available: true },
{ code: 'PSE', label: 'PSE', description: 'Bank debit', available: false },
]
<PaymentCatalog
methods={methods}
selected={selected}
onSelect={setSelected}
title="Payment methods"
/>Strategy pattern — pure validation
If you need to validate fields before sending to the backend, the library comes with a ready-to-use card validator and an extensible pattern for other methods:
import {
CardPaymentStrategy,
validateAndNormalizeCard,
} from '@andresh11/pay-preview/strategies'
const [error, normalized] = validateAndNormalizeCard({
number: '4111111111111111',
cardHolder: 'Andres Hernandez',
expMonth: '12',
expYear: '28',
cvc: '123',
})
if (error) {
console.error(error) // Spanish language error message
} else {
console.log(normalized)
// {
// method: 'CARD',
// number: '4111111111111111',
// numberFormatted: '4111 1111 1111 1111',
// cardHolder: 'Andres Hernandez',
// expMonth: '12',
// expYear: '28',
// cvc: '123',
// brand: 'visa',
// }
}Standalone detectors (without Strategy)
import { detectCardBrand, isValidLuhn, hasValidLength } from '@andresh11/pay-preview/core'
detectCardBrand('4111111111111111') // 'visa'
detectCardBrand('5105105105105100') // 'mastercard'
detectCardBrand('371449635398431') // 'amex'
detectCardBrand('6011111111111117') // 'generic'
isValidLuhn('4111111111111111') // true
hasValidLength('4111111111111111', 'visa') // trueExtending with your own method
import type {
IPaymentMethodStrategy,
IPaymentMethodMetadata,
TPaymentMethodCode,
TValidationResult,
} from '@andresh11/pay-preview'
import { PaymentStrategiesRegistry } from '@andresh11/pay-preview/strategies'
class MyCustomStrategy implements IPaymentMethodStrategy<TMyInput, TMyOutput> {
readonly code: TPaymentMethodCode = 'DAVIPLATA' // string literal
validate(raw: TMyInput): TValidationResult {
return raw.phone.length === 10 ? null : 'Invalid phone'
}
normalize(raw: TMyInput): TMyOutput {
return { method: 'DAVIPLATA', phone: raw.phone }
}
getMetadata(): IPaymentMethodMetadata {
return {
code: 'DAVIPLATA',
label: 'Daviplata',
description: 'Digital wallet',
enabled: true,
}
}
}
const registry = new PaymentStrategiesRegistry()
registry.register(new MyCustomStrategy())Tree-shaking
| Import | Min size | Gz size |
|---|---|---|
| CreditCard only (/react) | ~11 KB | ~4 KB |
| CreditCard + PaymentCatalog + Nequi (/react) | ~14 KB | ~5 KB |
| Full root (@andresh11/pay-preview) — strategies/core only | ~3 KB | ~1.5 KB |
The bundler (Vite, esbuild, Rollup) discards what you don't import, including the CSS if you don't import it.
CSS variables customization
All styles use the --pp- prefix (pay-preview). Override in your global CSS
or in a container selector:
:root {
/* ── CreditCard ── */
--pp-card-width: 360px;
--pp-card-height: 210px;
--pp-card-radius: 16px;
--pp-card-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
--pp-card-flip-duration: 600ms;
/* ── Brands ── */
--pp-brand-visa: linear-gradient(135deg, #ff6b6b, #4ecdc4);
--pp-brand-mastercard: linear-gradient(135deg, #e96443, #904e95);
--pp-brand-amex: linear-gradient(135deg, #006fcf, #00b7ff);
--pp-brand-generic: linear-gradient(135deg, #1c1d22, #5b86e5);
/* ── Nequi glow ── */
--pp-nq-glow-1: #2000a0;
--pp-nq-glow-2: #b900c5;
--pp-nq-text: #ffffff;
/* ── Bank method (PSE + Bancolombia) ── */
--pp-bm-card-bg: #ffffff;
--pp-bm-card-fg: #0f172a;
--pp-bm-accent: #4f46e5;
}Variables use fallback in the component, so you only need to define the ones you want.
Subpath exports
| Path | Exports | Peer deps |
|---|---|---|
| @andresh11/pay-preview | Strategies + detectors + types | none |
| @andresh11/pay-preview/react | React components | react, react-dom |
| @andresh11/pay-preview/vue | Vue components | vue (in your bundler) |
| @andresh11/pay-preview/svelte | Svelte components | svelte (in your bundler) |
| @andresh11/pay-preview/solid | Solid components | solid-js |
| @andresh11/pay-preview/react-native | React Native components | react, react-native |
| @andresh11/pay-preview/core | Standalone detectors | none |
| @andresh11/pay-preview/strategies | Strategies + card validator | none |
| @andresh11/pay-preview/styles/credit-card.css | CreditCard CSS | none |
| @andresh11/pay-preview/styles/nequi.css | NequiPreview CSS | none |
| @andresh11/pay-preview/styles/bank-method.css | PSE + Bancolombia CSS | none |
| @andresh11/pay-preview/styles/catalog.css | PaymentCatalog CSS | none |
Tech stack
- Mitosis 0.14 — one
.lite.tsxsource → 5 frameworks. - Strict TypeScript —
strict: true,noUncheckedIndexedAccess: true. - ESLint 8 +
eslint-plugin-perfectionist— 0 warnings. - No runtime dependencies — only
react/react-domas peer. - Zero polyfills — only native APIs already in modern browsers.
Development
npm install
npm run typecheck # tsc --noEmit
npm run lint # eslint --max-warnings 0
npm run build # mitosis → tsc → css → copy
npm run dev:demo # demo with HMR (http://localhost:5173)
npm run build:demo # demo bundle (final verification)
npm run validate:tree-shaking # verifies bundle < 30 KBStructure
src/
├── core/
│ ├── detectors/ ← detectCardBrand, isValidLuhn, hasValidLength
│ └── formatters/ ← formatCardNumber, formatExpMonth, etc.
├── mitosis/ ← .lite.tsx sources + CSS (generates 5 targets)
│ ├── CreditCard.lite.tsx
│ ├── NequiPreview.lite.tsx
│ ├── PSEPreview.lite.tsx
│ ├── BancolombiaTransferPreview.lite.tsx
│ ├── PaymentCatalog.lite.tsx
│ └── styles/ ← credit-card, nequi, bank-method, catalog
├── strategies/
│ ├── types/ ← IPaymentMethodStrategy, PaymentStrategiesRegistry
│ └── card/ ← CardPaymentStrategy + validateAndNormalizeCard
└── types/ ← pure public typesBuild flow (multi-target)
src/mitosis/*.lite.tsx
│
├── build-mitosis.mjs ──→ 5 targets × N components
│ (react, vue, svelte, solid, reactNative)
│
├── build-ts.mjs ────────→ dist/{index,core,strategies,types}/*
│
├── build-css.mjs ───────→ dist/styles/*.css (minified)
│
└── copy-mitosis.mjs ────→ dist/{target}/* (bundle per target
+ index.d.ts autogenerated)copy-mitosis.mjs also generates an index.d.ts per target by extracting
the export interface and export type declarations from each .lite.tsx,
so TypeScript can type the props without having to parse Mitosis JSX.
Compatibility notes
Mitosis 0.14 + Windows
Mitosis build on Windows has 5 documented workarounds:
--forcebreaks the path parser → don't use it; cleansrc/components/first.--out-dirignores the correct extension with multiple files: invoke mitosis file by file and capture stdout.mitosis.config.jsonwithtargets[]does NOT work on Windows.--state useState(not defaultuseLocalProxy) to emit idiomatic React code withoutimport { useLocalProxy } from '@builder.io/mitosis'."use client"is auto-emitted when usinguseStore()in React: post-process and remove it before exposing.
ESLint 8.57
Does NOT support ignoreTypeLikeDeclarations or ignoreFunctionDeclarations
(from the perfectionist plugin). We use ESLint 8.57 without those rules.
Licencia / License
MIT © Andres Hernandez — ver LICENSE.
🤝 Contribuir / Contributing
- Fork & branch.
npm install && npm run dev:demoto see live changes.npm run typecheck && npm run lintbefore commit.- Pull request describing which method or feature you add.
Important: if you're going to add a new payment method (Daviplata, Caja Social, BNPL, etc.), open an issue first to discuss the layout of the preview UI. The library keeps 5 previews and an extensible catalog, not 10 custom components.
