# Stripe Connect — Marketplaces y Plataformas

Stripe Connect permite que una plataforma facilite pagos entre compradores
y vendedores (o prestadores de servicio), reteniendo una comisión y pagando
a los vendedores automáticamente. Es el sistema detrás de marketplaces como
Airbnb, Etsy o plataformas de freelancers.

---

## Modelo de negocio: plataforma → vendedor → cliente

```
Cliente
  |
  | paga $1,000 MXN
  v
Plataforma (cuenta Stripe principal)
  |
  | transfiere $900 MXN al vendedor
  | retiene $100 MXN como comisión
  v
Vendedor (cuenta Stripe Connect — "connected account")
```

La plataforma es responsable ante Stripe de los pagos. Los vendedores reciben
payouts directamente en su cuenta bancaria según el schedule configurado.

---

## Tipos de cargos en Connect

### 1. Destination Charges (recomendado para la mayoría de casos)

El cargo se crea en la cuenta de la **plataforma** y se transfiere al vendedor.
La plataforma es responsable de disputas y reembolsos.

```python
import stripe
from app.core.config import settings

stripe.api_key = settings.STRIPE_SECRET_KEY


async def crear_cobro_marketplace(
    monto_total_centavos: int,
    comision_plataforma_centavos: int,
    stripe_account_id_vendedor: str,
    orden_id: str,
) -> stripe.PaymentIntent:
    """
    Crea un Destination Charge.
    El cliente paga monto_total; el vendedor recibe (monto_total - comision).
    """
    return stripe.PaymentIntent.create(
        amount=monto_total_centavos,
        currency="mxn",
        # La comisión que retiene la plataforma
        application_fee_amount=comision_plataforma_centavos,
        # La cuenta Connect del vendedor recibe el resto
        transfer_data={
            "destination": stripe_account_id_vendedor,
        },
        metadata={"orden_id": orden_id},
        idempotency_key=f"pi_marketplace_{orden_id}",
    )
```

### 2. Direct Charges (para mayor autonomía del vendedor)

El cargo se crea directamente en la cuenta del **vendedor**. La plataforma
toma su comisión vía `application_fee_amount`. El vendedor maneja disputas.
Requiere que el cliente conozca la marca del vendedor.

```python
async def crear_cobro_directo(
    monto_centavos: int,
    comision_centavos: int,
    stripe_account_id_vendedor: str,
    orden_id: str,
) -> stripe.PaymentIntent:
    """Cargo directo en la cuenta del vendedor."""
    return stripe.PaymentIntent.create(
        amount=monto_centavos,
        currency="mxn",
        application_fee_amount=comision_centavos,
        metadata={"orden_id": orden_id},
        idempotency_key=f"pi_direct_{orden_id}",
        stripe_account=stripe_account_id_vendedor,  # Clave: ejecutar en cuenta del vendedor
    )
```

### 3. Separate Charges and Transfers (control máximo)

Cobra al cliente en la cuenta de la plataforma y crea transferencias
independientes a uno o varios vendedores. Útil cuando un pedido tiene
productos de múltiples vendedores.

```python
async def distribuir_pago_multi_vendedor(
    payment_intent_id: str,
    distribuciones: list[dict],  # [{"account": "acct_xxx", "amount": 500}]
) -> list[stripe.Transfer]:
    """
    Transfiere partes del pago a múltiples vendedores.
    Llamar DESPUÉS de que el PaymentIntent sea exitoso.
    """
    transferencias = []
    for dist in distribuciones:
        transferencia = stripe.Transfer.create(
            amount=dist["amount"],
            currency="mxn",
            destination=dist["account"],
            source_transaction=payment_intent_id,
            idempotency_key=f"transfer_{payment_intent_id}_{dist['account']}",
        )
        transferencias.append(transferencia)
    return transferencias
```

---

## Onboarding de vendedores: AccountLink para KYC

Stripe requiere verificar la identidad de los vendedores (KYC — Know Your Customer)
antes de permitirles recibir pagos. Este proceso se realiza vía AccountLink.

### Paso 1: Crear la cuenta Connect del vendedor

```python
async def crear_cuenta_vendedor(
    email: str,
    usuario_id: str,
) -> stripe.Account:
    """
    Crea una cuenta Connect de tipo 'express' (recomendado).
    - express: Stripe maneja el onboarding UI (dashboard simplificado para el vendedor)
    - standard: el vendedor tiene acceso completo al dashboard de Stripe
    - custom: la plataforma controla toda la UI (requiere más trabajo y responsabilidad)
    """
    cuenta = stripe.Account.create(
        type="express",
        email=email,
        country="MX",
        capabilities={
            "card_payments": {"requested": True},
            "transfers": {"requested": True},
        },
        business_type="individual",
        metadata={"usuario_id": usuario_id},
    )
    # Guardar cuenta.id como stripe_connect_account_id en la BD del vendedor
    return cuenta


async def generar_link_onboarding(
    stripe_account_id: str,
    usuario_id: str,
) -> str:
    """
    Genera un AccountLink para que el vendedor complete el KYC.
    El link expira en ~10 minutos — generar justo antes de redirigir.
    """
    link = stripe.AccountLink.create(
        account=stripe_account_id,
        refresh_url=f"{settings.BASE_URL}/vendedores/onboarding/reiniciar",
        return_url=f"{settings.BASE_URL}/vendedores/onboarding/completado",
        type="account_onboarding",
    )
    return link.url


async def verificar_onboarding_completo(stripe_account_id: str) -> bool:
    """Verifica si el vendedor completó el KYC y puede recibir pagos."""
    cuenta = stripe.Account.retrieve(stripe_account_id)
    return (
        cuenta.details_submitted
        and cuenta.charges_enabled
        and cuenta.payouts_enabled
    )
```

### Flujo de onboarding completo

```
1. Vendedor hace clic en "Conectar cuenta de pagos"
2. Backend llama crear_cuenta_vendedor() → guarda stripe_connect_account_id
3. Backend llama generar_link_onboarding() → redirige al vendedor a Stripe
4. Vendedor completa KYC en Stripe (CURP, datos bancarios, etc.)
5. Stripe redirige a return_url
6. Backend llama verificar_onboarding_completo() para confirmar
7. Stripe envía webhook account.updated cuando cambia el estado
```

---

## Splits de pago: application_fee_amount

La comisión de la plataforma se especifica en `application_fee_amount` (centavos).
Esta comisión se retiene automáticamente antes de transferir al vendedor.

```python
# Ejemplo: orden de $1,000 MXN con 10% de comisión de plataforma
monto_total = 100_000       # $1,000.00 MXN en centavos
comision = 10_000           # $100.00 MXN (10%)
# El vendedor recibe: $900.00 MXN

# Cálculo dinámico de comisión
def calcular_comision(monto_centavos: int, porcentaje: float) -> int:
    """Retorna la comisión en centavos, redondeando hacia arriba."""
    import math
    return math.ceil(monto_centavos * porcentaje / 100)
```

---

## Payout schedule para vendedores

Por defecto, Stripe realiza payouts automáticos a las cuentas bancarias de
los vendedores. Se puede configurar el schedule por cuenta:

```python
async def configurar_payout_schedule(
    stripe_account_id: str,
    intervalo: str = "weekly",  # "daily" | "weekly" | "monthly"
    dia_semana: str = "friday",  # Para weekly
) -> stripe.Account:
    """
    Configura cuándo recibe payouts el vendedor.
    - daily: cada día hábil (requiere cuenta activa por ≥ 7 días)
    - weekly: el día especificado de la semana
    - monthly: el día especificado del mes (1-31)
    """
    return stripe.Account.modify(
        stripe_account_id,
        settings={
            "payouts": {
                "schedule": {
                    "interval": intervalo,
                    "weekly_anchor": dia_semana,
                }
            }
        },
        stripe_account=stripe_account_id,
    )
```

---

## Webhooks críticos para Connect

Además de los webhooks estándar, Connect genera eventos para cuentas conectadas:

```python
# El header stripe-signature sigue siendo obligatorio verificar
# Los eventos de cuentas conectadas incluyen account.updated

match evento.type:
    case "account.updated":
        # Verificar si el vendedor completó onboarding
        cuenta = evento.data.object
        await actualizar_estado_vendedor(cuenta.id, cuenta.charges_enabled)
    case "account.application.deauthorized":
        # El vendedor desconectó la plataforma — revocar acceso
        await revocar_vendedor(evento.data.object.id)
    case "transfer.created":
        # Transferencia enviada al vendedor
        await registrar_transferencia(evento.data.object)
    case "payout.paid":
        # El vendedor recibió el dinero en su banco
        await marcar_payout_pagado(evento.data.object)
    case "payout.failed":
        # El payout falló (cuenta bancaria inválida, etc.)
        await notificar_payout_fallido(evento.data.object)
```

---

## MUST DO / MUST NOT DO en Connect

**MUST DO:**
- Guardar `stripe_connect_account_id` en la BD del vendedor desde el momento de creación.
- Verificar `charges_enabled` Y `payouts_enabled` antes de permitir transacciones.
- Manejar `account.updated` para actualizar el estado de onboarding en tiempo real.
- Escalar `application_fee_amount` según la lógica de negocio de cada transacción.

**MUST NOT DO:**
- NUNCA crear un transfer sin `source_transaction` — vincula la transferencia al cobro.
- NUNCA hacer payouts manuales cuando el schedule automático es suficiente.
- NUNCA asumir que el return_url significa que el onboarding está completo — verificar con la API.
- NUNCA compartir el `stripe_account_id` de un vendedor con otros vendedores.
