# Ambiente de pruebas --- - [Ambiente de pruebas](#sandbox) - [Cómo funciona el modo prueba](#como) - [Tarjetas de prueba](#tarjetas) - [Rechazos por causal](#tarjetas-causal) - [3D Secure en pruebas](#tarjetas-3ds) - [Otros medios de pago](#otros-medios) - [Qué no se puede probar](#limites) - [Lista de verificación antes de producción](#checklist) ## Cómo funciona el modo prueba [#como] Con un [token de prueba](/authentication#ambientes) **nada se envía a la red de pagos**: el resultado del pago lo decides tú, eligiendo qué tarjeta usas. Todo lo demás se comporta igual que en producción — se crea la transacción, se guarda el detalle, se dispara el webhook, aparece en tu reporte — solo que no hay dinero de por medio. Eso te deja probar el camino completo, incluido el manejo de rechazos, que es la mitad del trabajo de una integración y la que nadie prueba. :::note No hay una URL distinta para pruebas. Es la misma API; el token decide el ambiente. ::: ## Tarjetas de prueba [#tarjetas] El resultado depende del **par número + CVV**. Si mandas un número de esta lista con otro CVV, la transacción sale como tarjeta no registrada (ver abajo). Cualquier fecha de vencimiento futura sirve. ### Aprueban | Franquicia | Número | CVV | | - | - | - | | Mastercard | `5249 3140 2334 0339` | `478` | | Visa | `4485 9021 7887 7927` | `963` | | American Express | `3402 564352 97046` | `4405` | | Diners Club | `3013 190041 2377` | `870` | ### Rechazan (rechazo genérico) Devuelven `response_code: "05"`, el rechazo genérico del emisor, con `error.action: "contact_issuer"`, `retryable: false` y la `description` «Tu banco no autorizó el pago. Comunícate con tu banco o usa otro medio de pago.». | Franquicia | Número | CVV | | - | - | - | | Mastercard | `5163 8852 8716 0861` | `705` | | Visa | `4315 8923 8199 8014` | `950` | | American Express | `3723 190710 51332` | `7048` | | Diners Club | `3000 614052 3128` | `725` | ### Rechazan por causal [#tarjetas-causal] Para probar cómo reacciona tu integración a cada motivo de rechazo. Todas son Visa, con CVV `123` y cualquier vencimiento futuro, y quedan en estado `Rechazada`. | Número | `response_code` | Causal | `description` | `retryable` | `action` | | - | - | - | - | - | - | | `4000 0000 0000 9995` | `51` | Fondos insuficientes | Transacción declinada. Fondos insuficientes | `false` | `contact_issuer` | | `4000 0000 0000 0069` | `54` | Tarjeta vencida | Tu tarjeta está vencida. Usa otra tarjeta. | `false` | `use_another_card` | | `4000 0000 0000 0127` | `98` | CVV incorrecto | El código de seguridad (CVV) es incorrecto. Verifica los datos de la tarjeta. | `false` | `check_card_data` | | `4000 0000 0000 9987` | `43` | Tarjeta bloqueada por el emisor | Esta tarjeta está bloqueada. Usa otra tarjeta. | `false` | `use_another_card` | | `4000 0000 0000 0119` | `91` | Emisor no disponible (error de red) | Tu banco no respondió. Intenta de nuevo en unos minutos. | `true` | `retry_later` | :::note En todas las tarjetas de causal (y en las de rechazo genérico) la `description` y el `error.action` piden lo mismo: puedes mostrar la `description` tal cual y decidir con `action` sin que se contradigan. `error.message` nombra la causa para tu log, no para el cliente. ::: ### Qué devuelve un rechazo [#respuesta-rechazo] Un rechazo en pruebas tiene **exactamente la forma de producción**: `status`, `status_key`, `response_code`, el objeto `error` y una `description` pensada para el tarjetahabiente. Así el código que escribes contra el sandbox es el mismo que corre en producción. ```json { "transaction_id": 20482, "status": "Rechazada", "status_key": "rejected", "response_code": "98", "error": { "code": "98", "message": "Transacción declinada. CVV inválido.", "retryable": false, "action": "check_card_data" }, "description": "El código de seguridad (CVV) es incorrecto. Verifica los datos de la tarjeta.", "…": "resto de campos de la transacción", "save": true, "payment_id": "EL_PAYMENT_ID", "transaction": { "…": "mismos campos de la raíz" } } ``` Los campos de decisión están en la raíz y también en `transaction`; lee `status_key` en la raíz. Ver [la respuesta del checkout](/checkout-transaction#ejemplo-para-tarjetas). El mismo objeto `error` llega en los [webhooks de transacción](/webhook-transaction) y, en suscripciones, dentro de `transaction.error` del webhook [`retry`](/subscription-webhook). Los valores de `action` y cuándo `retryable` es `true` están en [Códigos de error](/error-codes#red). :::caution Cualquier otra tarjeta —incluida una tarjeta real— o un número de la lista con otro CVV **no se aprueba** en modo prueba. Responde `status_key: "failed"`, `response_code: null`, `error: null` y la `description` «Tarjeta no registrada en el ambiente de pruebas». No es un error de tu integración. ::: **Ejemplo de un cobro que aprueba:** ```bash curl -X POST \ '/api/v1/payment/transaction-checkout/card' \ -H 'Authorization: Bearer TU_TOKEN_DE_PRUEBA' \ -H 'Content-type: application/json' \ -d '{ "payment": { "id": "EL_PAYMENT_ID", "token": "EL_TOKEN" }, "customer_payer": { "...": "..." }, "payment_card": { "number": "4485902178877927", "name": "ANA GOMEZ", "expiration_date": "2030-12", "cvv": "963", "installments": 1 } }' ``` ## 3D Secure en pruebas [#tarjetas-3ds] El flujo de [3D Secure](/checkout-transaction#flujo-3ds) tiene sus propias tarjetas, porque lo que se prueba no es la aprobación sino el resultado de la **autenticación**: que tu integración sepa qué hacer cuando el banco pide un reto, y cuando el cliente lo abandona. Están documentadas en la [sección de 3DS del checkout](/checkout-transaction#flujo-3ds). ## Otros medios de pago [#otros-medios] | Medio | En modo prueba | | - | - | | Tarjeta | Resultado según la tarjeta de la lista | | PSE | Se genera la redirección; el banco de pruebas te deja elegir el resultado | | Efectivo | Se genera el cupón. Nadie lo paga, así que la transacción queda en `Por Pagar` | | Bre-B / Nequi / DaviPlata | Se genera la solicitud; el pago no se confirma | ## Qué no se puede probar [#limites] - **Reservas de cupo en modalidad `api`.** Retienen fondos reales; no hay simulación. Responden `MIT_PRODUCTION_KEY_REQUIRED`. Ver [reserva de cupo](/mit-pre-authorization). - **Abonos a tu cuenta virtual.** Una transacción de prueba nunca genera saldo, así que tampoco hay transferencias que consultar. - **Reversiones y anulaciones reales.** Existen contra la red, no contra el simulador. ## Lista de verificación antes de producción [#checklist] Antes de cambiar el token, comprueba que probaste lo que va a pasar de verdad: - [ ] Un pago **aprobado** de punta a punta, con el webhook recibido y procesado. - [ ] Un pago **rechazado**, y que tu sistema no deja el pedido colgado. - [ ] El estado **`Pendiente`**: que no lo trates como rechazo. Es el error más caro. - [ ] La **verificación de la firma** del webhook, rechazando un cuerpo alterado. - [ ] Un webhook **repetido**: reintentamos, así que tu endpoint tiene que ser idempotente. - [ ] La consulta de estado como respaldo, para cuando el webhook no llegue. - [ ] Que guardas nuestro `transaction_id` junto a tu pedido, para poder conciliar. :::tip Cuando cambies al token de producción no tienes que cambiar ninguna URL ni ningún campo: solo el token. :::