This article is currently available in Spanish only. English translation coming soon!
Integración de Pagos en Tu Web: Guía Completa Stripe, PayPal y Más
Todo sobre pasarelas de pago para tu web. Comparativa, costos, seguridad y cómo implementar pagos online correctamente.
Integración de Pagos en Tu Web: Guía Completa 2025
El 70% de carritos abandonados se deben a procesos de pago complicados o falta de confianza. Una buena pasarela de pagos puede aumentar tus conversiones un 35-50%.
Pasarelas de Pago Principales
Stripe ⭐⭐⭐⭐⭐
const stripePros = {
pros: [
"Mejor experiencia de desarrollador",
"Documentación excelente",
"APIs modernas y potentes",
"Checkout embebido y hosted",
"Subscripciones nativas",
"Soporte de 135+ monedas",
"Fraude detection incluido"
],
contras: [
"Requiere conocimientos técnicos",
"No tan conocido para usuarios finales"
],
costos: {
transaccion: "1.4% + 0.25€",
internacional: "+1.5%",
subscripción: "0.5% adicional",
setup: "0€"
},
ideal: "E-commerce modernos, SaaS, subscripciones"
}
PayPal ⭐⭐⭐⭐
const paypalInfo = {
pros: [
"Muy conocido (confianza)",
"No requiere tarjeta (saldo PayPal)",
"Protección comprador/vendedor",
"Setup muy simple",
"Aceptado globalmente"
],
contras: [
"UX puede ser disruptiva (redirige)",
"Comisiones más altas",
"Retención de fondos sin aviso",
"Soporte regular"
],
costos: {
nacional: "2.9% + 0.35€",
internacional: "3.4% + 0.35€ + conversión",
setup: "0€"
},
ideal: "PyMEs, primeros pasos e-commerce"
}
Redsys (España) ⭐⭐⭐
const redsysInfo = {
pros: [
"Estándar bancario España",
"Integra con tu banco",
"Comisiones negociables",
"3D Secure nativo"
],
contras: [
"Setup complejo",
"Documentación confusa",
"UX anticuada",
"Requiere contrato con banco"
],
costos: {
transaccion: "0.8% - 1.5% (negociable)",
setup: "100€ - 500€",
mantenimiento: "20€ - 50€/mes"
},
ideal: "Empresas establecidas, volumen alto"
}
Comparativa Rápida
| Pasarela | Comisión | Setup | Ideal Para |
|----------|----------|-------|------------|
| Stripe | 1.4% + 0.25€ | 0€ | E-commerce moderno, SaaS |
| PayPal | 2.9% + 0.35€ | 0€ | PyMEs, inicial |
| Redsys | 0.8-1.5% | 500€ | Volumen alto España |
| Square | 1.75% + 0.15€ | 0€ | Retail + Online |
| Bizum | 0.5% | Variable | Pagos P2P España |
Implementación Stripe (Recomendado)
Checkout Embebido
// Frontend - Crear sesión de pago
const createCheckoutSession = async (items) => {
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items })
})
const { sessionId } = await response.json()
// Redirigir a Stripe Checkout
const stripe = Stripe('pk_live_xxxxx')
const { error } = await stripe.redirectToCheckout({ sessionId })
if (error) {
console.error('Error:', error)
}
}
// Backend - Endpoint Node.js/Express
app.post('/api/create-checkout-session', async (req, res) => {
const { items } = req.body
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: items.map(item => ({
price_data: {
currency: 'eur',
product_data: {
name: item.name,
images: [item.image]
},
unit_amount: item.price * 100 // céntimos
},
quantity: item.quantity
})),
mode: 'payment',
success_url: `${process.env.DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.DOMAIN}/cancel`,
shipping_address_collection: {
allowed_countries: ['ES', 'PT', 'FR']
},
metadata: {
orderId: generateOrderId()
}
})
res.json({ sessionId: session.id })
})
// Webhook para confirmar pago
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature']
let event
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
)
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`)
}
// Handle evento
if (event.type === 'checkout.session.completed') {
const session = event.data.object
// Marcar pedido como pagado
await fulfillOrder(session)
}
res.json({ received: true })
})
Payment Element (Custom)
// Más control sobre el diseño
import { loadStripe } from '@stripe/stripe-js'
import {
Elements,
PaymentElement,
useStripe,
useElements
} from '@stripe/react-stripe-js'
const stripePromise = loadStripe('pk_live_xxxxx')
function CheckoutForm() {
const stripe = useStripe()
const elements = useElements()
const handleSubmit = async (e) => {
e.preventDefault()
if (!stripe || !elements) return
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: 'https://tu-sitio.com/order-complete'
}
})
if (error) {
setErrorMessage(error.message)
}
}
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
<button disabled={!stripe}>Pagar</button>
</form>
)
}
// En tu componente principal
function Checkout() {
const [clientSecret, setClientSecret] = useState('')
useEffect(() => {
// Crear Payment Intent
fetch('/api/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 5000 }) // 50€
})
.then(res => res.json())
.then(data => setClientSecret(data.clientSecret))
}, [])
return (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm />
</Elements>
)
}
Seguridad (PCI DSS Compliance)
Nunca Hagas Esto
// ❌ NUNCA guardes datos de tarjeta
const badPractice = {
cardNumber: '4242424242424242', // ILEGAL
cvv: '123', // ILEGAL
expiry: '12/25' // ILEGAL
}
// ❌ NUNCA envíes datos de tarjeta a tu servidor
fetch('/api/payment', {
method: 'POST',
body: JSON.stringify({
cardNumber: document.getElementById('card').value // PELIGROSO
})
})
Hazlo Correctamente
// ✅ Usa tokens/Payment Methods
const stripe = Stripe('pk_live_xxxxx')
// Los datos de tarjeta van directamente a Stripe
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement, // Elemento de Stripe, no input HTML
billing_details: {
name: customerName,
email: customerEmail
}
})
// Solo envías el ID del payment method a tu servidor
fetch('/api/process-payment', {
method: 'POST',
body: JSON.stringify({
paymentMethodId: paymentMethod.id, // Solo el ID, seguro
amount: 5000
})
})
Subscripciones Recurrentes
Setup con Stripe
// Crear un producto y precio
const product = await stripe.products.create({
name: 'Plan Pro Mensual',
description: 'Acceso completo a todas las funcionalidades'
})
const price = await stripe.prices.create({
product: product.id,
unit_amount: 2900, // 29€
currency: 'eur',
recurring: {
interval: 'month'
}
})
// Crear subscripción
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: price.id }],
payment_behavior: 'default_incomplete',
expand: ['latest_invoice.payment_intent']
})
// Manejar cancelaciones
await stripe.subscriptions.update(subscriptionId, {
cancel_at_period_end: true
})
// Cambiar plan
await stripe.subscriptions.update(subscriptionId, {
items: [{
id: subscriptionItem.id,
price: newPriceId
}],
proration_behavior: 'create_prorations' // Prorratear diferencia
})
Prevención de Fraude
Stripe Radar (Incluido)
const paymentIntentConfig = {
amount: 5000,
currency: 'eur',
// Radar analiza automáticamente
metadata: {
orderId: '123',
customerEmail: 'customer@email.com'
},
// Forzar 3D Secure para pagos arriesgados
payment_method_options: {
card: {
request_three_d_secure: 'automatic' // o 'any'
}
}
}
// Revisar nivel de riesgo
const paymentIntent = await stripe.paymentIntents.retrieve(id)
const riskLevel = paymentIntent.charges.data[0].outcome.risk_level
// 'normal', 'elevated', 'highest'
if (riskLevel === 'highest') {
// Revisar manualmente o rechazar
}
Reglas Personalizadas
**Block automático si:**
- Email temporal (guerrillamail, etc.)
- IP de país no vendemos
- Múltiples tarjetas rechazadas
- Velocidad sospechosa (10 intentos/minuto)
**Review manual si:**
- Primera compra > 200€
- Envío != Facturación (países diferentes)
- Compra muy por encima de ticket promedio
Costos Reales
Ejemplo E-commerce
const costosEcommerce = {
ventas: {
mensuales: 50000, // 50K€/mes
tickets: 200, // 200 pedidos
promedio: 250 // 250€ pedido promedio
},
conStripe: {
comision: "(50.000 * 0.014) + (200 * 0.25) = 750€",
porcentaje: "1.5% de ventas"
},
conPayPal: {
comision: "(50.000 * 0.029) + (200 * 0.35) = 1.520€",
porcentaje: "3% de ventas"
},
conRedsys: {
comision: "(50.000 * 0.01) + 30€ mensualidad = 530€",
porcentaje: "1.06% de ventas",
nota: "Requiere setup 500€ inicial"
},
ahorroStripeVsPayPal: "770€/mes = 9.240€/año"
}
Testing
Tarjetas de Prueba Stripe
const testCards = {
success: {
number: '4242 4242 4242 4242',
description: 'Siempre aprueba'
},
decline: {
number: '4000 0000 0000 0002',
description: 'Siempre rechaza'
},
authentication: {
number: '4000 0027 6000 3184',
description: 'Requiere 3D Secure'
},
insufficient: {
number: '4000 0000 0000 9995',
description: 'Fondos insuficientes'
}
}
// Cualquier CVV y fecha futura funcionan en modo test
Checklist Implementación
## Básico ✅
- [ ] Pasarela elegida y cuenta creada
- [ ] API keys (test y live) obtenidas
- [ ] SSL certificado instalado (HTTPS obligatorio)
- [ ] Términos y condiciones actualizados
- [ ] Política de devoluciones clara
- [ ] Emails transaccionales configurados
## Seguridad ✅
- [ ] Nunca guardamos datos de tarjeta
- [ ] PCI DSS compliance verificado
- [ ] Webhooks con verificación de firma
- [ ] 3D Secure habilitado
- [ ] Rate limiting en endpoints
- [ ] Logs de todas las transacciones
## UX ✅
- [ ] Loading states claros
- [ ] Mensajes de error útiles
- [ ] Confirmación inmediata
- [ ] Email confirmación enviado
- [ ] Redirección después de pago
- [ ] Mobile optimizado
## Legal ✅
- [ ] Política de privacidad (GDPR)
- [ ] Aviso legal completo
- [ ] Política de cookies
- [ ] IVA incluido en precio (B2C España)
- [ ] Facturación automática
## Testing ✅
- [ ] Pagos exitosos
- [ ] Pagos rechazados
- [ ] 3D Secure
- [ ] Webhooks funcionando
- [ ] Emails llegando
- [ ] Múltiples navegadores
- [ ] Mobile devices
Solución de Problemas Comunes
Pagos Rechazados
const handleDeclineReasons = {
'insufficient_funds': {
mensaje: "Tu tarjeta no tiene fondos suficientes",
accion: "Usa otra tarjeta o método de pago"
},
'card_declined': {
mensaje: "Tu banco rechazó el pago",
accion: "Contacta a tu banco o usa otra tarjeta"
},
'expired_card': {
mensaje: "Tu tarjeta ha expirado",
accion: "Actualiza los datos de tu tarjeta"
},
'incorrect_cvc': {
mensaje: "El código CVV es incorrecto",
accion: "Verifica el código de 3 dígitos"
},
'processing_error': {
mensaje: "Error procesando el pago",
accion: "Intenta de nuevo en unos minutos"
}
}
// Mostrar mensaje amigable al usuario
if (error.decline_code) {
const userMessage = handleDeclineReasons[error.decline_code]
showError(userMessage.mensaje)
}
Conclusión
Una buena integración de pagos es crítica para tu negocio online:
✅ Confianza: SSL + Pasarela conocida = +35% conversión ✅ Seguridad: PCI DSS compliance protege tu negocio ✅ UX: Proceso fluido = menos abandono ✅ Costos: Elegir bien ahorra miles al año
Recomendación:
- Empezando: Stripe o PayPal
- Volumen alto: Redsys + Stripe
- SaaS: Stripe (subscripciones nativas)
- Marketplaces: Stripe Connect
¿Necesitas integrar pagos en tu web?
Solicita un presupuesto de integración
“El mejor proceso de pago es el que el cliente ni nota.”