Payments E-commerce Web Development Business Stripe PayPal Security

Integración de Pagos en Tu Web: Guía Completa Stripe, PayPal y Más

Pablo Alcalde García

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.”

¿Te ha gustado este artículo?

Si tienes preguntas o quieres discutir sobre estos temas, no dudes en contactarme.

Contáctame
Escríbeme por WhatsApp