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 Tarjeta | Número de Tarjeta de Prueba | Comportamiento Esperado |
---|---|---|
Visa | 4111111111111111 | Pago exitoso |
Mastercard | 5555555555554444 | Pago exitoso |
Amex | 378282246310005 | Pago exitoso |
Rechazada | 4000000000000002 | Transacció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:
Escenario | Tarjeta de Prueba | CVV | ZIP |
---|---|---|---|
Éxito | 4111111111111111 | 123 | 12345 |
Rechazada - Fondos Insuficientes | 4000000000009995 | 123 | 12345 |
Rechazada - Tarjeta Perdida | 4000000000009987 | 123 | 12345 |
Autenticación 3D Secure | 4000000000003220 | 123 | 12345 |
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:
- Crear cuentas de probador sandbox en App Store Connect
- Configurar diferentes tipos de cuentas (usuarios nuevos, suscriptores existentes)
- 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 exitosaandroid.test.canceled
- Simula compra canceladaandroid.test.refunded
- Simula compra reembolsadaandroid.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
Pasarela | Configuración de Modo de Prueba | Tarjetas de Prueba Proporcionadas |
---|---|---|
Stripe | Usar claves API de prueba con prefijo sk_test_ | Suite completa de tarjetas de prueba |
PayPal | Entorno Sandbox de PayPal | Cuentas sandbox requeridas |
Braintree | ID de comerciante sandbox | Números de tarjeta de prueba proporcionados |
Square | Token de acceso sandbox | Tarjetas de prueba para varios escenarios |
Adyen | Cuenta de comerciante de prueba | Nú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 Prueba | Tarjeta de Prueba | Resultado Esperado |
---|---|---|
Número inválido | 4242424242424241 | Error de validación de tarjeta |
Tarjeta expirada | Exp: 01/2020 | Error de tarjeta expirada |
CVV inválido | CVV: 99 | Error de CVV inválido |
ZIP inválido | ZIP: 00000 | Error 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:
Escenario | Desde Plan | A Plan | Comportamiento Esperado |
---|---|---|---|
Actualización | Básico ($9.99) | Premium ($19.99) | Actualización inmediata, cargo prorrateado |
Degradación | Premium ($19.99) | Básico ($9.99) | Cambio al final del período |
Cambio cruzado | Mensual ($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ón | Métodos de Pago | Requisitos de Prueba |
---|---|---|
Norteamérica | Tarjetas de crédito, Apple Pay, Google Pay, PayPal | Probar todas las redes principales |
Europa | SEPA, Tarjetas de crédito, Apple Pay, Google Pay, Bancontact | Flujo de débito directo SEPA |
Asia | Alipay, WeChat Pay, Tarjetas de crédito, Line Pay | Flujo de pago con código QR |
Latinoamérica | Tarjetas de crédito, Mercado Pago, PIX, OXXO | Vouchers 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:
- Usar entornos sandbox extensivamente - Aprovechar entornos de prueba de Apple, Google y pasarelas de pago para validar funcionalidad sin riesgo financiero
- Probar todos los escenarios de fallo - Problemas de red, tarjetas rechazadas, timeouts y casos extremos deben probarse exhaustivamente
- Priorizar pruebas de seguridad - Cumplimiento PCI DSS, encriptación de datos y transmisión segura son críticos
- Automatizar donde sea posible - Integrar pruebas de pago en pipelines CI/CD para validación continua
- Probar entre plataformas y regiones - Diferentes plataformas, divisas y métodos de pago regionales requieren cobertura de prueba específica
- 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.