Los sistemas de pago móvil han revolucionado la forma en que los usuarios (como se discute en Cross-Platform Mobile Testing: Strategies for Multi-Device Success) (como se discute en Appium 2.0: New Architecture and Cloud Integration for Modern Mobile Testing) realizan transacciones en smartphones y tablets. Desde pagos sin contacto con Apple Pay y Google Pay hasta compras in-app y modelos de suscripción, las pruebas de pagos móviles requieren conocimientos especializados de estándares de seguridad, implementaciones específicas de plataforma y requisitos de cumplimiento. Esta guía completa cubre todo lo que los ingenieros QA necesitan saber sobre pruebas efectivas de sistemas de pago móvil.

Introducción a los Desafíos de Pruebas de Pago Móvil

Las pruebas de pago móvil presentan desafíos únicos que difieren significativamente de las pruebas tradicionales de pago web. La complejidad proviene de múltiples factores:

Fragmentación de Plataformas: Diferentes sistemas operativos (iOS, Android) (como se discute en Detox: Grey-Box Testing for React Native Applications) implementan sistemas de pago de manera diferente, requiriendo enfoques de prueba específicos por plataforma.

Requisitos de Seguridad: Los sistemas de pago deben cumplir con PCI DSS (Estándar de Seguridad de Datos de la Industria de Tarjetas de Pago), requiriendo pruebas de seguridad rigurosas y medidas de protección de datos.

Limitaciones del Sandbox: Los entornos de prueba a menudo tienen funcionalidad restringida comparada con producción, haciendo las pruebas integrales desafiantes.

Dependencias de Terceros: Las pasarelas de pago, bancos y proveedores de plataforma introducen dependencias externas que pueden afectar la confiabilidad de las pruebas.

Criticidad de Experiencia de Usuario: Los flujos de pago deben ser fluidos e intuitivos, ya que la fricción en el proceso de pago impacta directamente las tasas de conversión y los ingresos.

Los desafíos clave incluyen:

  • Probar sin exponer credenciales de pago reales
  • Validar encriptación y transmisión segura de datos
  • Simular varios escenarios de pago (éxito, fallo, timeout)
  • Probar en diferentes dispositivos, versiones de SO y condiciones de red
  • Asegurar cumplimiento con regulaciones de pago regionales
  • Validar flujos de reembolso y cancelación
  • Probar lógica de suscripción y pagos recurrentes

Pruebas e Integración de Apple Pay

La integración de Apple Pay requiere estrategias de prueba específicas debido a las restricciones del ecosistema y el modelo de seguridad.

Configuración del Entorno de Prueba de Apple Pay

Apple proporciona entornos sandbox para probar transacciones de Apple Pay:

// Habilitar modo sandbox en app iOS
#if DEBUG
    let configuration = PKPaymentAuthorizationConfiguration()
    configuration.merchantIdentifier = "merchant.com.tuapp.sandbox"
    configuration.supportedNetworks = [.visa, .masterCard, .amex]
    configuration.merchantCapabilities = .capability3DS
#else
    // Configuración de producción
#endif

Configuración de Tarjetas de Prueba

Apple proporciona tarjetas de prueba a través del entorno Sandbox:

Tipo de TarjetaNúmero de Tarjeta de PruebaComportamiento Esperado
Visa4111111111111111Pago exitoso
Mastercard5555555555554444Pago exitoso
Amex378282246310005Pago exitoso
Rechazada4000000000000002Transacción rechazada

Escenarios de Prueba Críticos para Apple Pay

1. Flujo de Autorización de Pago

func testApplePayAuthorization() {
    let request = PKPaymentRequest()
    request.merchantIdentifier = "merchant.com.tuapp"
    request.supportedNetworks = [.visa, .masterCard, .amex]
    request.merchantCapabilities = .capability3DS
    request.countryCode = "US"
    request.currencyCode = "USD"

    let item = PKPaymentSummaryItem(label: "Producto de Prueba", amount: NSDecimalNumber(string: "9.99"))
    request.paymentSummaryItems = [item]

    // Verificar presentación del controlador de autorización
    let controller = PKPaymentAuthorizationController(paymentRequest: request)
    controller.delegate = self
    controller.present { presented in
        XCTAssertTrue(presented, "El controlador de pago debe presentarse")
    }
}

2. Pruebas de Autenticación Biométrica

Probar Apple Pay con Face ID/Touch ID:

  • Autenticación biométrica válida
  • Intentos fallidos de autenticación
  • Respaldo a entrada de código
  • Configuraciones biométricas deshabilitadas

3. Pruebas de Compatibilidad de Dispositivos

Verificar soporte de Apple Pay en:

  • Modelos de iPhone (iPhone 6 y posteriores)
  • Integración de Apple Watch
  • Modelos de iPad con Touch ID/Face ID
  • Mac con Touch ID

Lista de Verificación de Validación de Apple Pay

  • Entorno sandbox configurado correctamente
  • Identificador de comerciante de prueba activo
  • Hoja de pago muestra cantidad y nombre de comerciante correcto
  • Redes de tarjetas soportadas configuradas apropiadamente
  • Autenticación 3D Secure funcionando
  • Recibo de transacción generado correctamente
  • Manejo de errores para pagos rechazados
  • Escenarios de timeout de red manejados
  • Validación de dirección de envío (si aplica)
  • Recolección de información de contacto funcionando

Estrategias de Prueba de Google Pay

Google Pay ofrece un entorno de prueba más flexible comparado con Apple Pay, con amplias capacidades de sandbox.

Configuración del Entorno de Prueba de Google Pay

// Configuración de Google Pay en Android
private fun createPaymentsClient(): PaymentsClient {
    val walletOptions = Wallet.WalletOptions.Builder()
        .setEnvironment(WalletConstants.ENVIRONMENT_TEST) // Usar entorno de prueba
        .build()
    return Wallet.getPaymentsClient(this, walletOptions)
}

private fun createPaymentDataRequest(): PaymentDataRequest {
    val request = PaymentDataRequest.fromJson(
        """
        {
            "apiVersion": 2,
            "apiVersionMinor": 0,
            "allowedPaymentMethods": [
                {
                    "type": "CARD",
                    "parameters": {
                        "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"],
                        "allowedCardNetworks": ["MASTERCARD", "VISA"]
                    },
                    "tokenizationSpecification": {
                        "type": "PAYMENT_GATEWAY",
                        "parameters": {
                            "gateway": "example",
                            "gatewayMerchantId": "exampleMerchantId"
                        }
                    }
                }
            ],
            "merchantInfo": {
                "merchantId": "TEST_MERCHANT_ID",
                "merchantName": "Comerciante de Prueba"
            },
            "transactionInfo": {
                "totalPriceStatus": "FINAL",
                "totalPrice": "12.34",
                "currencyCode": "USD"
            }
        }
        """.trimIndent()
    )
    return request
}

Tarjetas de Prueba de Google Pay

Google proporciona tarjetas de prueba completas para varios escenarios:

EscenarioTarjeta de PruebaCVVZIP
Éxito411111111111111112312345
Rechazada - Fondos Insuficientes400000000000999512312345
Rechazada - Tarjeta Perdida400000000000998712312345
Autenticación 3D Secure400000000000322012312345

Escenarios de Prueba Clave de Google Pay

1. Disponibilidad del Método de Pago

@Test
fun testGooglePayAvailability() {
    val isReadyToPayRequest = IsReadyToPayRequest.fromJson(
        """
        {
            "apiVersion": 2,
            "apiVersionMinor": 0,
            "allowedPaymentMethods": [
                {
                    "type": "CARD",
                    "parameters": {
                        "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"],
                        "allowedCardNetworks": ["MASTERCARD", "VISA"]
                    }
                }
            ]
        }
        """.trimIndent()
    )

    paymentsClient.isReadyToPay(isReadyToPayRequest)
        .addOnCompleteListener { task ->
            assertTrue("Google Pay debe estar disponible", task.isSuccessful)
        }
}

2. Validación de Token

Verificar estructura y encriptación del token de pago:

fun validatePaymentToken(token: String) {
    val jsonToken = JSONObject(token)

    // Verificar campos requeridos
    assertTrue(jsonToken.has("signature"))
    assertTrue(jsonToken.has("protocolVersion"))
    assertTrue(jsonToken.has("signedMessage"))

    // Verificar validez de firma
    val signedMessage = jsonToken.getString("signedMessage")
    assertNotNull(signedMessage)
    assertFalse(signedMessage.isEmpty())
}

Pruebas de Compras In-App (IAP) para iOS y Android

Las compras in-app requieren enfoques de prueba específicos por plataforma para consumibles, no consumibles, suscripciones y contenido auto-renovable.

Pruebas de Compras In-App en iOS

Configuración de Probador Sandbox:

  1. Crear cuentas de probador sandbox en App Store Connect
  2. Configurar diferentes tipos de cuentas (usuarios nuevos, suscriptores existentes)
  3. Probar con varias regiones de cuenta para localización
// Pruebas de StoreKit en iOS
import StoreKit

class IAPManager: NSObject, SKPaymentTransactionObserver {
    func purchaseProduct(productId: String) {
        guard SKPaymentQueue.canMakePayments() else {
            print("El usuario no puede hacer pagos")
            return
        }

        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
    }

    func paymentQueue(_ queue: SKPaymentQueue,
                     updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchased:
                // Verificar recibo y desbloquear contenido
                completeTransaction(transaction)
            case .failed:
                // Manejar fallo
                failedTransaction(transaction)
            case .restored:
                // Restaurar compra anterior
                restoreTransaction(transaction)
            case .deferred:
                // Pago pendiente de aprobación
                print("Pago diferido")
            case .purchasing:
                print("Comprando...")
            @unknown default:
                break
            }
        }
    }
}

Casos de Prueba de IAP en iOS:

  • Recuperación de productos desde App Store
  • Completación del flujo de compra
  • Validación de recibo (local y del lado del servidor)
  • Restauración de transacciones
  • Transacciones interrumpidas (terminación de app durante compra)
  • Soporte de Compartir en Familia
  • Ofertas promocionales y precios introductorios

Pruebas de Facturación In-App en Android

La Biblioteca de Facturación de Google Play proporciona herramientas de prueba completas:

class BillingManager(private val context: Context) : PurchasesUpdatedListener {
    private lateinit var billingClient: BillingClient

    fun initializeBilling() {
        billingClient = BillingClient.newBuilder(context)
            .setListener(this)
            .enablePendingPurchases()
            .build()

        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(result: BillingResult) {
                if (result.responseCode == BillingClient.BillingResponseCode.OK) {
                    // Consultar productos disponibles
                    queryProducts()
                }
            }

            override fun onBillingServiceDisconnected() {
                // Reintentar conexión
            }
        })
    }

    override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
        when (result.responseCode) {
            BillingClient.BillingResponseCode.OK -> {
                purchases?.forEach { purchase ->
                    if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                        // Reconocer compra
                        acknowledgePurchase(purchase)
                    }
                }
            }
            BillingClient.BillingResponseCode.USER_CANCELED -> {
                // Manejar cancelación del usuario
            }
            else -> {
                // Manejar otros errores
            }
        }
    }
}

IDs de Compra de Prueba en Android:

Google proporciona IDs de producto reservados para pruebas:

  • android.test.purchased - Compra siempre exitosa
  • android.test.canceled - Simula compra cancelada
  • android.test.refunded - Simula compra reembolsada
  • android.test.item_unavailable - Simula producto no disponible

Pruebas de Integración de Pasarela de Pago

La integración de pasarela de pago requiere probar la comunicación entre tu app, servidor backend y procesador de pagos.

Lista de Verificación de Pruebas de Pasarela de Pago

Puntos de Integración a Probar:

// Ejemplo de procesamiento de pago backend (Node.js)
const stripe = require('stripe')(process.env.STRIPE_TEST_KEY);

async function processPayment(paymentData) {
    try {
        // Crear intención de pago
        const paymentIntent = await stripe.paymentIntents.create({
            amount: paymentData.amount,
            currency: 'usd',
            payment_method_types: ['card'],
            metadata: {
                orderId: paymentData.orderId,
                userId: paymentData.userId
            }
        });

        // Confirmar pago
        const confirmedPayment = await stripe.paymentIntents.confirm(
            paymentIntent.id,
            {
                payment_method: paymentData.paymentMethodId
            }
        );

        return {
            success: true,
            transactionId: confirmedPayment.id,
            status: confirmedPayment.status
        };
    } catch (error) {
        return {
            success: false,
            error: error.message,
            code: error.code
        };
    }
}

Pasarelas de Pago Comunes y Entornos de Prueba

PasarelaConfiguración de Modo de PruebaTarjetas de Prueba Proporcionadas
StripeUsar claves API de prueba con prefijo sk_test_Suite completa de tarjetas de prueba
PayPalEntorno Sandbox de PayPalCuentas sandbox requeridas
BraintreeID de comerciante sandboxNúmeros de tarjeta de prueba proporcionados
SquareToken de acceso sandboxTarjetas de prueba para varios escenarios
AdyenCuenta de comerciante de pruebaNúmeros de tarjeta de prueba por región

Escenarios de Prueba de Integración API

# Ejemplo de prueba de integración en Python
import unittest
import requests

class PaymentGatewayTests(unittest.TestCase):
    def setUp(self):
        self.base_url = "https://api.payment-gateway.test"
        self.api_key = "test_api_key_12345"

    def test_successful_payment(self):
        """Probar procesamiento de pago exitoso"""
        payload = {
            "amount": 1000,  # Cantidad en centavos
            "currency": "USD",
            "card": {
                "number": "4242424242424242",
                "exp_month": 12,
                "exp_year": 2025,
                "cvc": "123"
            }
        }

        response = requests.post(
            f"{self.base_url}/charges",
            json=payload,
            headers={"Authorization": f"Bearer {self.api_key}"}
        )

        self.assertEqual(response.status_code, 200)
        self.assertTrue(response.json()["success"])

    def test_insufficient_funds(self):
        """Probar pago rechazado por fondos insuficientes"""
        payload = {
            "amount": 1000,
            "currency": "USD",
            "card": {
                "number": "4000000000009995",  # Tarjeta rechazada
                "exp_month": 12,
                "exp_year": 2025,
                "cvc": "123"
            }
        }

        response = requests.post(
            f"{self.base_url}/charges",
            json=payload,
            headers={"Authorization": f"Bearer {self.api_key}"}
        )

        self.assertEqual(response.status_code, 402)
        self.assertEqual(response.json()["error_code"], "insufficient_funds")

Pruebas de Cumplimiento PCI DSS

El cumplimiento PCI DSS (Estándar de Seguridad de Datos de la Industria de Tarjetas de Pago) es obligatorio para aplicaciones que manejan datos de tarjetas de pago.

Requisitos Clave de PCI DSS para Apps Móviles

1. Nunca Almacenar Datos de Autenticación Sensibles:

  • Los códigos CVV/CVC nunca deben almacenarse
  • Los datos completos de banda magnética están prohibidos
  • Los bloques PIN no deben retenerse

2. Encriptar Datos de Titular de Tarjeta:

// Ejemplo de encriptación en iOS
import CryptoKit

func encryptCardData(_ cardNumber: String, publicKey: String) throws -> Data {
    let key = try P256.KeyAgreement.PublicKey(pemRepresentation: publicKey)
    let symmetricKey = SymmetricKey(size: .bits256)

    let sealedBox = try AES.GCM.seal(
        cardNumber.data(using: .utf8)!,
        using: symmetricKey
    )

    return sealedBox.combined!
}

3. Implementar Controles de Acceso Fuertes:

  • Acceso basado en roles a datos de pago
  • Autenticación multifactor para acceso admin
  • Registro de auditoría de operaciones de pago

Lista de Verificación de Pruebas PCI DSS

  • No hay datos de tarjeta en texto plano almacenados en bases de datos
  • No hay datos sensibles en logs o mensajes de error
  • Encriptación SSL/TLS para transmisión de datos (TLS 1.2+)
  • Tokenización implementada para almacenamiento de tarjetas
  • Memoria limpiada después de procesamiento de pago
  • No hay datos de tarjeta en reportes de crash o analíticas
  • Prácticas de codificación segura seguidas
  • Escaneo de seguridad regular realizado
  • Pruebas de penetración conducidas anualmente
  • Documentación de procedimientos de seguridad mantenida

Pruebas de Fuga de Datos

# Verificar datos sensibles en logs
grep -r "4[0-9]{15}" ./logs/  # Buscar posibles números de tarjeta de crédito
grep -r "cvv\|cvc" ./logs/    # Buscar referencias CVV

# Analizar tráfico de app con proxy
mitmproxy -p 8080  # Interceptar tráfico HTTPS para verificar encriptación

Entornos Sandbox y de Prueba

El uso efectivo de entornos sandbox es crucial para pruebas completas de pago sin riesgo financiero.

Mejores Prácticas para Pruebas en Sandbox

1. Aislamiento de Entorno:

# Gestión de configuración
environments:
  development:
    payment_gateway: "sandbox"
    api_key: "${SANDBOX_API_KEY}"
    endpoint: "https://sandbox.payment-gateway.com"

  staging:
    payment_gateway: "sandbox"
    api_key: "${STAGING_API_KEY}"
    endpoint: "https://sandbox.payment-gateway.com"

  production:
    payment_gateway: "production"
    api_key: "${PRODUCTION_API_KEY}"
    endpoint: "https://api.payment-gateway.com"

2. Banderas de Características para Pruebas de Pago:

// Implementación de bandera de característica
const config = {
    useSandbox: process.env.NODE_ENV !== 'production',
    enableTestCards: process.env.ENABLE_TEST_CARDS === 'true',
    skipReceiptValidation: process.env.SKIP_RECEIPT_VALIDATION === 'true'
};

function getPaymentClient() {
    return config.useSandbox
        ? new SandboxPaymentClient(config.sandboxApiKey)
        : new ProductionPaymentClient(config.productionApiKey);
}

Limitaciones de Sandbox a Considerar

Limitaciones comunes de sandbox:

  • Soporte limitado para webhooks y callbacks
  • Límites de tasa reducidos comparados con producción
  • Paridad de características incompleta (algunas características avanzadas no disponibles)
  • Persistencia de datos puede ser temporal
  • Simulación de 3D Secure puede estar simplificada

Soluciones alternativas:

  • Servidor mock para pruebas de webhook
  • Pruebas de límite de tasa en entorno similar a producción
  • Documentar limitaciones conocidas de sandbox en planes de prueba

Pruebas de Seguridad para Flujos de Pago

Las pruebas de seguridad para sistemas de pago requieren técnicas especializadas más allá de las pruebas estándar de seguridad de aplicaciones.

Pruebas de Ataque Man-in-the-Middle (MITM)

# Prueba de certificate pinning
import ssl
import socket

def test_certificate_pinning(hostname, port, expected_cert_hash):
    """Verificar que la app implementa certificate pinning"""
    context = ssl.create_default_context()

    with socket.create_connection((hostname, port)) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            cert_der = ssock.getpeercert(binary_form=True)
            cert_hash = hashlib.sha256(cert_der).hexdigest()

            assert cert_hash == expected_cert_hash, \
                "Hash de certificado no coincide - posible ataque MITM"

Pruebas de Exposición de Datos Sensibles

Probar fuga de datos en varias ubicaciones:

# Inspección de iOS Keychain
security dump-keychain ~/Library/Keychains/login.keychain-db

# Verificación de SharedPreferences en Android
adb shell run-as com.tuapp.package cat /data/data/com.tuapp.package/shared_prefs/preferences.xml

# Análisis de volcado de memoria
fridump -U com.tuapp.package -s
grep -r "4[0-9]{15}" dump/  # Buscar números de tarjeta en memoria

Pruebas de Seguridad de Sesión

Casos de prueba para seguridad de sesión de pago:

  • Timeout de sesión después de inactividad
  • Invalidación de sesión después de completar pago
  • Prevención de sesiones concurrentes
  • Validación de token CSRF
  • Prevención de ataques de replay
// Prueba de gestión de sesión
describe('Seguridad de Sesión de Pago', () => {
    it('debe expirar sesión después de 15 minutos de inactividad', async () => {
        const session = await createPaymentSession();

        // Esperar período de timeout
        await sleep(15 * 60 * 1000);

        // Intentar usar sesión expirada
        const result = await processPayment(session.id);

        expect(result.error).toBe('session_expired');
    });

    it('debe prevenir ataques de replay', async () => {
        const payment = await capturePaymentRequest();

        // Procesar pago
        await processPayment(payment);

        // Intentar replay del mismo pago
        const replayResult = await processPayment(payment);

        expect(replayResult.error).toBe('duplicate_transaction');
    });
});

Pruebas de Escenarios de Fallo de Pago

Las pruebas exhaustivas de escenarios de fallo son esenciales para sistemas de pago robustos.

Escenarios de Fallo Comunes

1. Fallos de Red:

// Simular timeout de red
@Test
fun testPaymentNetworkTimeout() {
    // Mockear capa de red para simular timeout
    mockWebServer.enqueue(MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE))

    val result = paymentService.processPayment(testPaymentData)

    assertTrue(result is PaymentResult.NetworkError)
    assertEquals("Request timeout", result.message)
}

2. Fondos Insuficientes:

func testInsufficientFunds() {
    let testCard = TestCard(number: "4000000000009995") // Tarjeta rechazada

    let expectation = XCTestExpectation(description: "Pago rechazado")

    paymentProcessor.process(card: testCard, amount: 100.00) { result in
        switch result {
        case .failure(let error):
            XCTAssertEqual(error, .insufficientFunds)
            expectation.fulfill()
        case .success:
            XCTFail("El pago debería haber sido rechazado")
        }
    }

    wait(for: [expectation], timeout: 10.0)
}

3. Detalles de Tarjeta Inválidos:

Escenario de PruebaTarjeta de PruebaResultado Esperado
Número inválido4242424242424241Error de validación de tarjeta
Tarjeta expiradaExp: 01/2020Error de tarjeta expirada
CVV inválidoCVV: 99Error de CVV inválido
ZIP inválidoZIP: 00000Error de código postal no coincide

Pruebas de Recuperación de Errores

// Probar lógica de reintento automático
async function testPaymentRetry() {
    let attempts = 0;
    const maxRetries = 3;

    // Mockear servicio de pago para fallar dos veces, éxito en tercer intento
    paymentService.process = jest.fn()
        .mockRejectedValueOnce(new Error('Fallo temporal'))
        .mockRejectedValueOnce(new Error('Fallo temporal'))
        .mockResolvedValueOnce({ success: true, transactionId: '12345' });

    const result = await paymentWithRetry(paymentData, maxRetries);

    expect(paymentService.process).toHaveBeenCalledTimes(3);
    expect(result.success).toBe(true);
}

Pruebas de Suscripción y Pagos Recurrentes

Los modelos de suscripción requieren pruebas especializadas para varios eventos del ciclo de vida.

Escenarios de Prueba del Ciclo de Vida de Suscripción

1. Creación de Nueva Suscripción:

func testSubscriptionCreation() {
    let subscription = Subscription(
        productId: "monthly_premium",
        period: .monthly,
        price: 9.99
    )

    subscriptionManager.subscribe(to: subscription) { result in
        XCTAssertTrue(result.isSuccess)
        XCTAssertNotNil(result.receiptData)
        XCTAssertEqual(result.expirationDate, expectedDate)
    }
}

2. Pruebas de Renovación:

@Test
fun testAutoRenewal() = runTest {
    // Crear suscripción que expira pronto
    val subscription = createTestSubscription(
        expiresAt = now().plusMinutes(5)
    )

    // Esperar ventana de auto-renovación
    advanceTimeBy(6.minutes)

    // Verificar suscripción renovada
    val renewed = subscriptionRepository.getSubscription(subscription.id)
    assertTrue(renewed.isActive)
    assertEquals(now().plusMonths(1), renewed.expiresAt)
}

3. Actualización/Degradación de Suscripción:

EscenarioDesde PlanA PlanComportamiento Esperado
ActualizaciónBásico ($9.99)Premium ($19.99)Actualización inmediata, cargo prorrateado
DegradaciónPremium ($19.99)Básico ($9.99)Cambio al final del período
Cambio cruzadoMensual ($9.99)Anual ($99.99)Crédito prorrateado aplicado

Pruebas de Ciclos de Facturación

def test_billing_cycle():
    """Probar facturación de suscripción a través de múltiples ciclos"""
    subscription = create_subscription(
        user_id="test_user",
        plan="monthly",
        start_date=datetime(2025, 1, 1)
    )

    # Probar primera facturación
    first_charge = process_billing_cycle(subscription, datetime(2025, 1, 1))
    assert first_charge.amount == 9.99
    assert first_charge.status == "succeeded"

    # Probar segunda facturación
    second_charge = process_billing_cycle(subscription, datetime(2025, 2, 1))
    assert second_charge.amount == 9.99
    assert second_charge.status == "succeeded"

    # Probar pago fallido
    set_payment_method_invalid(subscription)
    third_charge = process_billing_cycle(subscription, datetime(2025, 3, 1))
    assert third_charge.status == "failed"
    assert subscription.status == "past_due"

Período de Gracia y Lógica de Reintento

describe('Período de Gracia de Suscripción', () => {
    it('debe reintentar pagos fallidos durante período de gracia', async () => {
        const subscription = await createSubscription({
            gracePeriodDays: 7,
            retryAttempts: 3
        });

        // Simular pago fallido
        await simulateFailedPayment(subscription);

        // Verificar estado de suscripción
        expect(subscription.status).toBe('past_due');
        expect(subscription.gracePeriodEnds).toBe(addDays(now(), 7));

        // Simular intentos de reintento
        for (let i = 1; i <= 3; i++) {
            await advanceTime(2, 'days');
            const retryResult = await retryPayment(subscription);

            if (i < 3) {
                expect(retryResult.attempt).toBe(i);
                expect(subscription.status).toBe('past_due');
            }
        }

        // Verificar suscripción cancelada después de período de gracia
        await advanceTime(1, 'days');
        expect(subscription.status).toBe('cancelled');
    });
});

Pruebas de Reembolso y Cancelación

El procesamiento de reembolsos y flujos de cancelación requieren pruebas cuidadosas para asegurar reconciliación financiera correcta.

Escenarios de Prueba de Reembolso

class RefundTests(unittest.TestCase):
    def test_full_refund(self):
        """Probar reembolso completo de pago"""
        # Crear y procesar pago
        payment = create_payment(amount=100.00)
        process_payment(payment)

        # Procesar reembolso completo
        refund = create_refund(
            payment_id=payment.id,
            amount=100.00,
            reason="customer_request"
        )

        self.assertEqual(refund.status, "succeeded")
        self.assertEqual(refund.amount, 100.00)
        self.assertEqual(payment.status, "refunded")

    def test_partial_refund(self):
        """Probar reembolso parcial de pago"""
        payment = create_payment(amount=100.00)
        process_payment(payment)

        # Procesar reembolso parcial
        refund = create_refund(
            payment_id=payment.id,
            amount=50.00,
            reason="partial_return"
        )

        self.assertEqual(refund.status, "succeeded")
        self.assertEqual(refund.amount, 50.00)
        self.assertEqual(payment.amount_refunded, 50.00)
        self.assertEqual(payment.status, "partially_refunded")

    def test_refund_deadline(self):
        """Probar que reembolso no puede procesarse después de fecha límite"""
        # Crear pago hace 6 meses
        payment = create_payment(
            amount=100.00,
            created_at=datetime.now() - timedelta(days=180)
        )

        # Intentar reembolso
        with self.assertRaises(RefundError) as context:
            create_refund(payment_id=payment.id, amount=100.00)

        self.assertIn("refund_deadline_exceeded", str(context.exception))

Pruebas de Cancelación

func testSubscriptionCancellation() {
    let subscription = createActiveSubscription()

    // Probar cancelación inmediata
    subscriptionManager.cancel(subscription, immediately: true) { result in
        XCTAssertTrue(result.isSuccess)
        XCTAssertEqual(subscription.status, .cancelled)
        XCTAssertNil(subscription.nextBillingDate)
        XCTAssertTrue(subscription.accessExpiresAt <= Date())
    }

    // Probar cancelación al final del período
    let subscription2 = createActiveSubscription()
    subscriptionManager.cancel(subscription2, immediately: false) { result in
        XCTAssertTrue(result.isSuccess)
        XCTAssertEqual(subscription2.status, .cancelledAtPeriodEnd)
        XCTAssertNotNil(subscription2.accessExpiresAt)
        XCTAssertTrue(subscription2.accessExpiresAt > Date())
    }
}

Pruebas de Múltiples Divisas y Localización

Las apps móviles que sirven audiencias globales deben manejar múltiples divisas y localización correctamente.

Escenarios de Prueba de Divisas

const currencyTestCases = [
    { currency: 'USD', amount: 100.00, formatted: '$100.00' },
    { currency: 'EUR', amount: 100.00, formatted: '100,00 €' },
    { currency: 'GBP', amount: 100.00, formatted: '£100.00' },
    { currency: 'JPY', amount: 10000, formatted: '¥10,000' },
    { currency: 'RUB', amount: 7500, formatted: '7 500 ₽' }
];

describe('Formato de Divisas', () => {
    currencyTestCases.forEach(testCase => {
        it(`debe formatear ${testCase.currency} correctamente`, () => {
            const formatted = formatCurrency(
                testCase.amount,
                testCase.currency
            );
            expect(formatted).toBe(testCase.formatted);
        });
    });
});

Pruebas de Tipo de Cambio

@Test
fun testCurrencyConversion() {
    val converter = CurrencyConverter()

    // Mockear servicio de tipo de cambio
    whenever(exchangeRateService.getRate("USD", "EUR"))
        .thenReturn(0.85)

    val result = converter.convert(
        amount = 100.0,
        from = "USD",
        to = "EUR"
    )

    assertEquals(85.0, result, 0.01)
}

@Test
fun testPriceConsistency() {
    // Asegurar que precios son consistentes entre divisas
    val usdPrice = 9.99
    val eurPrice = 8.49
    val gbpPrice = 7.99

    val currentRate = exchangeRateService.getRate("USD", "EUR")
    val convertedPrice = usdPrice * currentRate

    // Permitir 5% de varianza para redondeo de precios
    assertTrue(abs(convertedPrice - eurPrice) / eurPrice < 0.05)
}

Pruebas de Método de Pago Regional

Diferentes regiones soportan diferentes métodos de pago:

RegiónMétodos de PagoRequisitos de Prueba
NorteaméricaTarjetas de crédito, Apple Pay, Google Pay, PayPalProbar todas las redes principales
EuropaSEPA, Tarjetas de crédito, Apple Pay, Google Pay, BancontactFlujo de débito directo SEPA
AsiaAlipay, WeChat Pay, Tarjetas de crédito, Line PayFlujo de pago con código QR
LatinoaméricaTarjetas de crédito, Mercado Pago, PIX, OXXOVouchers de pago en efectivo

Pruebas Automatizadas de Pago y CI/CD

Integrar pruebas de pago en pipelines CI/CD asegura validación continua de funcionalidad de pago.

Estructura de Suite de Pruebas Automatizadas

# .github/workflows/payment-tests.yml
name: Pruebas de Integración de Pago

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  payment-tests:
    runs-on: ubuntu-latest

    env:
      STRIPE_TEST_KEY: ${{ secrets.STRIPE_TEST_KEY }}
      PAYPAL_SANDBOX_CLIENT: ${{ secrets.PAYPAL_SANDBOX_CLIENT }}

    steps:
      - uses: actions/checkout@v3

      - name: Configurar Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Instalar dependencias
        run: npm ci

      - name: Ejecutar pruebas unitarias de pago
        run: npm run test:payment:unit

      - name: Ejecutar pruebas de integración de pago
        run: npm run test:payment:integration

      - name: Ejecutar pruebas de seguridad de pago
        run: npm run test:payment:security

      - name: Generar reporte de pruebas de pago
        if: always()
        run: npm run test:report

      - name: Subir resultados de pruebas
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: payment-test-results
          path: test-results/

Servidor de Pago Mock para Pruebas

// mock-payment-server.js
const express = require('express');
const app = express();

app.post('/v1/charges', (req, res) => {
    const { card, amount } = req.body;

    // Simular varios escenarios de tarjeta
    const testCards = {
        '4242424242424242': { success: true },
        '4000000000000002': { success: false, error: 'card_declined' },
        '4000000000009995': { success: false, error: 'insufficient_funds' },
        '4000000000000069': { success: false, error: 'expired_card' }
    };

    const result = testCards[card.number] || { success: true };

    if (result.success) {
        res.json({
            id: `ch_test_${Date.now()}`,
            amount: amount,
            status: 'succeeded',
            paid: true
        });
    } else {
        res.status(402).json({
            error: {
                type: 'card_error',
                code: result.error,
                message: getErrorMessage(result.error)
            }
        });
    }
});

app.listen(3001, () => {
    console.log('Servidor de pago mock ejecutándose en puerto 3001');
});

Pruebas de Rendimiento para Flujos de Pago

from locust import HttpUser, task, between

class PaymentLoadTest(HttpUser):
    wait_time = between(1, 3)

    @task(3)
    def create_payment_intent(self):
        """Probar creación de intención de pago bajo carga"""
        self.client.post("/v1/payment_intents", json={
            "amount": 1000,
            "currency": "usd",
            "payment_method_types": ["card"]
        }, headers={"Authorization": f"Bearer {self.api_key}"})

    @task(2)
    def confirm_payment(self):
        """Probar confirmación de pago bajo carga"""
        # Crear intención de pago primero
        response = self.client.post("/v1/payment_intents", json={
            "amount": 1000,
            "currency": "usd"
        }, headers={"Authorization": f"Bearer {self.api_key}"})

        if response.status_code == 200:
            intent_id = response.json()["id"]

            # Confirmar pago
            self.client.post(f"/v1/payment_intents/{intent_id}/confirm",
                json={"payment_method": "pm_card_visa"},
                headers={"Authorization": f"Bearer {self.api_key}"})

    @task(1)
    def retrieve_payment(self):
        """Probar rendimiento de recuperación de pago"""
        self.client.get("/v1/charges/ch_test_12345",
            headers={"Authorization": f"Bearer {self.api_key}"})

Monitoreo Continuo

// Monitorear salud de pago en producción
const monitor = {
    async checkPaymentHealth() {
        const metrics = {
            successRate: await this.calculateSuccessRate(),
            avgProcessingTime: await this.getAvgProcessingTime(),
            failureReasons: await this.getFailureBreakdown()
        };

        // Alertar si la tasa de éxito cae bajo el umbral
        if (metrics.successRate < 0.95) {
            this.sendAlert({
                severity: 'high',
                message: `Tasa de éxito de pago cayó a ${metrics.successRate}`,
                metrics: metrics
            });
        }

        return metrics;
    },

    async calculateSuccessRate() {
        const last24h = new Date(Date.now() - 24 * 60 * 60 * 1000);
        const payments = await Payment.find({
            createdAt: { $gte: last24h }
        });

        const successful = payments.filter(p => p.status === 'succeeded').length;
        return successful / payments.length;
    }
};

// Ejecutar verificación de salud cada 5 minutos
setInterval(() => monitor.checkPaymentHealth(), 5 * 60 * 1000);

Conclusión

Las pruebas de pago móvil requieren un enfoque integral que cubra implementaciones específicas de plataforma, cumplimiento de seguridad, entornos sandbox y varios escenarios de pago. Conclusiones clave para pruebas efectivas de pago:

  1. Usar entornos sandbox extensivamente - Aprovechar entornos de prueba de Apple, Google y pasarelas de pago para validar funcionalidad sin riesgo financiero
  2. Probar todos los escenarios de fallo - Problemas de red, tarjetas rechazadas, timeouts y casos extremos deben probarse exhaustivamente
  3. Priorizar pruebas de seguridad - Cumplimiento PCI DSS, encriptación de datos y transmisión segura son críticos
  4. Automatizar donde sea posible - Integrar pruebas de pago en pipelines CI/CD para validación continua
  5. Probar entre plataformas y regiones - Diferentes plataformas, divisas y métodos de pago regionales requieren cobertura de prueba específica
  6. Monitorear métricas de producción - Rastrear tasas de éxito de pago, tiempos de procesamiento y patrones de fallo en tiempo real

Siguiendo estas directrices e implementando cobertura de pruebas integral, los ingenieros QA pueden asegurar sistemas de pago móvil robustos, seguros y confiables que proporcionen excelentes experiencias de usuario mientras mantienen estrictos estándares de seguridad y cumplimiento.