Métodos de pago
Cambio importante en el API
Para la creación de transacciones y fuentes de pago, pensando en la privacidad y el correcto manejo de los datos personales de nuestros usuarios, es ahora obligatorio el uso de Tokens de Aceptación a la hora de crear cualquiera de estos dos recursos a través de nuestro API.
Cada vez que creas una transacción usando nuestro API, tienes la opción de procesar el pago usando distintos métodos de pago. Actualmente se encuentran disponibles los siguientes métodos de pago:
- Tarjetas de Crédito o Débito: Permite a tus clientes usar tarjetas de crédito o débito para realizar el pago.
- Clave: Ofrece a tus clientes la posibilidad de usar su tarjeta Clave, para completar el pago.
Para usar un método de pago al hacer POST en el endpoint de /transactions debes:
- Especificar el campo
payment_methodcon un objeto JSON que contiene detalles específicos de cada método, descritos más abajo.
Al finalizar el proceso de pago de cualquiera de los métodos disponibles, te recomendamos siempre verificar periódicamente (long polling) el estado de una transacción, esperando un status final (aprobada, rechazada o error), usando el ID de transacción y nuestro API, ya que ninguno de los métodos de pago otorga un resultado síncrono (inmediato). Una transacción recién creada siempre tiene un status: PENDING.
Estados finales de una transacción
El status final posible de una transacción puede ser: APPROVED (aprobada), DECLINED (rechazada), VOIDED (anulada, sólo aplica para transacciones con Tarjeta) o ERROR (si sucedió algún error externo a Wompi autorizando la transacción).
Tarjetas de Crédito o Débito
En Wompi, tus clientes pueden procesar pagos usando tarjetas crédito y débito de las franquicias Visa y MasterCard, siempre y cuando cuenten con un CVC (Código de Verificación de Tarjeta), usualmente impreso en el reverso de la tarjeta, lo que significa que está habilitada para comprar en internet.
El nombre del método de pago que debes usar al crear la transacción es CARD. Al usar el tipo de método de pago CARD debes tener en cuenta que:
- Debes haber tokenizado una tarjeta previamente (instrucciones a continuación)
- Debes haber preguntado a tu usuario en cuántas cuotas desea hacer el pago.
Tokeniza una tarjeta
Por defecto recomendamos tokenizar cifrando la información de la tarjeta. Si tu caso de uso no soporta el cifrado, selecciona la pestaña de tokenización simple.
- Cifrando la información
- Tokenización simple
Los pasos para tokenizar cifrando la información de la tarjeta son los siguientes:
- Obtén la llave pública (
GET /v1/tokens/keys/tokenization). - Genera el JWE con algoritmo RSA-OAEP-256 y CEK AES-GCM-256.
- Envía el
payload(el JWE como string) aPOST /v1/tokens/cards.
Para tokenizar cifrando la información (método recomendado), primero se debe generar un JWE (JSON Web Encryption) con la información de la tarjeta y enviarlo en el campo payload.
La llave pública necesaria para generar el JWE —la cual utiliza el algoritmo RSA-OAEP— puede obtenerse desde el siguiente endpoint:
GET /v1/tokens/keys/tokenization
Para realizar la tokenización de una tarjeta, se debe enviar la información de la tarjeta cifrada al siguiente endpoint.
POST /v1/tokens/cards
Usa la llave pública de tu comercio en el header de autorización.
"Authorization": "Bearer [llave pública del comercio]"
El JWE debe ser enviado dentro del campo payload como un string (base64) y para el CEK (Content Encryption Key) del JWE es necesario utilizar AES GCM 256.
Suponiendo una función cifrar_jwe, la cual genera el JWE a partir de la información de la tarjeta, la llave pública y el algoritmo a utilizar en el CEK, al endpoint se debe enviar la siguiente información:
{
"payload": cifrar_jwe(
{
"number": "4242424242424242", // Número de la tarjeta
"cvc": "123", // Código de seguridad de la tarjeta (3 o 4 dígitos según corresponda)
"exp_month": "08", // Mes de expiración (string de 2 dígitos)
"exp_year": "28", // Año expresado current 2 dígitos
"card_holder": "José Pérez" // Nombre del tarjetahabiente
},
LLAVE_PUBLICA_CIFRADO,
"RSA-OAEP-256"
)
}
La respuesta exitosa de v1/tokens/cards se verá de la siguiente manera:
{
"status": "CREATED",
"data": {
"id": "tok_prod_1_BBb749EAB32e97a2D058Dd538a608301", // TOKEN que debe ser usado para crear la transacción
"created_at": "2020-01-02T18:52:35.850+00:00",
"brand": "VISA",
"name": "VISA-4242",
"last_four": "4242",
"bin": "424242",
"exp_year": "28",
"exp_month": "08",
"card_holder": "José Pérez",
"expires_at": "2020-06-30T18:52:35.000Z"
}
}
Ejemplos en distintos lenguajes:
Para Panamá usa https://api.wompi.pa/v1 como BASE_URL en los ejemplos.
- JavaScript (Node)
- Python
- Java
- Php
import { pathToFileURL } from 'url'
import { importSPKI, EncryptJWT } from 'jose'
const BASE_URL = '<BASE_URL>'
const WOMPI_PUBLIC_KEY = '...'
const cardInfo = {
number: '4242424242424242',
exp_month: '04',
exp_year: '30',
cvc: '123',
card_holder: 'test test',
}
let cachedPublicKey
async function getPublicKey() {
if (cachedPublicKey) return cachedPublicKey
const response = await fetch(`${BASE_URL}/tokens/keys/tokenization`, {
headers: {
Authorization: `Bearer ${WOMPI_PUBLIC_KEY}`,
'Content-Type': 'application/json',
},
})
if (!response.ok) {
const body = await response.text()
throw new Error(`Unable to fetch public key. Status ${response.status}: ${body}`)
}
const body = await response.json()
const publicKeyPem = body?.data?.publicKey
if (!publicKeyPem) throw new Error('Public key missing in response')
cachedPublicKey = publicKeyPem
return cachedPublicKey
}
async function tokenizeCard(payload) {
const response = await fetch(`${BASE_URL}/tokens/cards`, {
method: 'POST',
headers: {
Authorization: `Bearer ${WOMPI_PUBLIC_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
const body = await response.json()
if (!response.ok) {
throw new Error(
`Tokenization failed - Status ${response.status}: ${JSON.stringify(body)}`
)
}
return body
}
export async function encryptCardInfo(cardInfo, pubKey) {
const secret = await importSPKI(pubKey, 'RSA-OAEP-256')
const encryptedData = await new EncryptJWT(cardInfo)
.setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' })
.encrypt(secret)
return encryptedData
}
const main = async () => {
try {
const publicKey = await getPublicKey()
const encrypted = await encryptCardInfo(cardInfo, publicKey)
console.log("encypted:", encrypted)
const tokenizationResponnse = await tokenizeCard({ payload: encrypted })
console.log(tokenizationResponnse)
return tokenizationResponnse
} catch (error) {
console.error('Tokenization failed:', error)
process.exitCode = 1
}
}
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
main()
}
import json
import requests
from jwcrypto import jwk, jwe
BASE_URL = "<BASE_URL>"
WOMPI_PUBLIC_KEY = "......"
CARD_INFO = {
"number": "4242424242424242",
"exp_month": "04",
"exp_year": "30",
"cvc": "123",
"card_holder": "test test",
}
PUBLIC_KEY_CACHE: bytes | None = None
def get_public_key() -> bytes:
global PUBLIC_KEY_CACHE
if PUBLIC_KEY_CACHE:
return PUBLIC_KEY_CACHE
response = requests.get(
f"{BASE_URL}/tokens/keys/tokenization",
headers={"Authorization": f"Bearer {WOMPI_PUBLIC_KEY}"},
)
response.raise_for_status()
payload = response.json()
public_key = payload.get("data", {}).get("publicKey")
if not public_key:
raise RuntimeError("Unable to fetch public key")
PUBLIC_KEY_CACHE = public_key.encode("utf-8")
return PUBLIC_KEY_CACHE
def encrypt(payload: dict, key: bytes | str) -> str:
pem_key = key.encode("utf-8") if isinstance(key, str) else key
public_jwk = jwk.JWK.from_pem(pem_key)
protected_header = {"alg": "RSA-OAEP-256", "enc": "A256GCM"}
jwe_token = jwe.JWE(json.dumps(payload).encode("utf-8"), protected=protected_header)
jwe_token.add_recipient(public_jwk)
return jwe_token.serialize(compact=True)
def tokenize(card_payload: dict) -> dict:
response = requests.post(
f"{BASE_URL}/tokens/cards",
data=json.dumps(card_payload),
headers={"Authorization": f"Bearer {WOMPI_PUBLIC_KEY}"},
)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
encrypted_card = encrypt(CARD_INFO, get_public_key())
print("JWE:", encrypted_card)
token = tokenize({"payload": encrypted_card})
print("Token:", token)
package com.wompi.cardtokenization;
// nimbus-jose-jwt
// bcpkix-jdk18on
// jackson-databind
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WompiCardTokenization {
private static final String BASE_URL = "<BASE_URL>";
private static final String WOMPI_PUBLIC_KEY = "...";
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
private static String PUBLIC_KEY_CACHE;
public static void main(String[] args) throws Exception {
Map<String, String> cardInfo = new LinkedHashMap<>() {{
put("number", "4242424242424242");
put("exp_month", "04");
put("exp_year", "30");
put("cvc", "123");
put("card_holder", "test test");
}};
String publicKeyPem = getPublicKey(BASE_URL);
String encryptedCard = encrypt(cardInfo, publicKeyPem);
System.out.println("JWE: " + encryptedCard);
JsonNode tokenResponse = tokenize(encryptedCard, BASE_URL, WOMPI_PUBLIC_KEY);
System.out.println("Token: " + tokenResponse.toPrettyString());
}
private static String getPublicKey(String baseUrl) throws Exception {
if (PUBLIC_KEY_CACHE != null) {
return PUBLIC_KEY_CACHE;
}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/tokens/keys/tokenization"))
.GET()
.header("Accept", "application/json")
.header("Authorization", "Bearer " + WOMPI_PUBLIC_KEY)
.timeout(Duration.ofSeconds(15))
.build();
HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
JsonNode json = MAPPER.readTree(response.body());
JsonNode publicKeyNode = json.path("data").path("publicKey");
if (publicKeyNode.isMissingNode() || publicKeyNode.isNull()) {
throw new IllegalStateException("Unable to retrieve public key: " + response.body());
}
String pemRaw = publicKeyNode.asText();
String pem = normalizePem(pemRaw);
PUBLIC_KEY_CACHE = pem;
return pem;
}
private static String encrypt(Map<String, String> payload, String pemKey) throws Exception {
try {
JWK jwk = JWK.parseFromPEMEncodedObjects(pemKey);
RSAKey rsaKey = jwk.toRSAKey();
JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
.contentType("application/json")
.build();
Payload jwePayload = new Payload(MAPPER.writeValueAsString(payload));
var jweObject = new com.nimbusds.jose.JWEObject(header, jwePayload);
jweObject.encrypt(new RSAEncrypter(rsaKey));
return jweObject.serialize();
} catch (Exception e) {
String preview = pemKey == null ? "null" : pemKey.substring(0, Math.min(120, pemKey.length()));
throw new IllegalStateException("Failed to parse public key. Starts with: " + preview, e);
}
}
private static String normalizePem(String raw) {
if (raw == null || raw.isBlank()) {
return raw;
}
String cleaned = raw
.replace("\\n", "\n")
.replace("\r", "")
.trim();
String base64 = cleaned
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
return "-----BEGIN PUBLIC KEY-----\n" + base64 + "\n-----END PUBLIC KEY-----";
}
private static JsonNode tokenize(String encryptedPayload, String baseUrl, String wompiPublicKey) throws Exception {
Map<String, String> body = Map.of("payload", encryptedPayload);
String requestBody = MAPPER.writeValueAsString(body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/tokens/cards"))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + wompiPublicKey)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
return MAPPER.readTree(response.body());
}
}
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
// "require": {
// "web-token/jwt-encryption": "^3.4",
// "web-token/jwt-key-mgmt": "^3.4",
// "ext-json": "*",
// "ext-openssl": "*"
// }
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256GCM;
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256;
use Jose\Component\Encryption\Compression\CompressionMethodManager;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\Encryption\Serializer\CompactSerializer;
use Jose\Component\KeyManagement\JWKFactory;
const BASE_URL = '<BASE_URL>';
const WOMPI_PUBLIC_KEY = '...';
$cardInfo = [
'number' => '4242424242424242',
'exp_month' => '04',
'exp_year' => '30',
'cvc' => '123',
'card_holder' => 'test test',
];
$publicKeyCache = null;
function getPublicKey(): string
{
global $publicKeyCache;
if ($publicKeyCache !== null) {
return $publicKeyCache;
}
$ch = curl_init(BASE_URL . '/tokens/keys/tokenization');
curl_setopt_array($ch, [
CURLOPT_HTTPGET => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . WOMPI_PUBLIC_KEY,
],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false || $status < 200 || $status >= 300) {
throw new RuntimeException('Unable to fetch public key, status: ' . $status);
}
$decoded = json_decode($response, true);
$pem = $decoded['data']['publicKey'] ?? null;
if (!$pem) {
throw new RuntimeException('Public key missing in response');
}
$pem = normalizePem($pem);
$publicKeyCache = $pem;
return $publicKeyCache;
}
function tokenize(string $encryptedPayload): array
{
$body = json_encode(['payload' => $encryptedPayload], JSON_THROW_ON_ERROR);
$ch = curl_init(BASE_URL . '/tokens/cards');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . WOMPI_PUBLIC_KEY,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) {
throw new RuntimeException('Tokenization request failed to execute');
}
$decoded = json_decode($response, true);
if ($status < 200 || $status >= 300) {
throw new RuntimeException('Tokenization failed with status ' . $status . ': ' . $response);
}
return $decoded;
}
function normalizePem(string $raw): string
{
$cleaned = str_replace(["\r", '\\n'], ['', "\n"], trim($raw));
$base64 = preg_replace(
'/\s+/',
'',
str_replace(['-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----'], '', $cleaned)
);
return "-----BEGIN PUBLIC KEY-----\n" . $base64 . "\n-----END PUBLIC KEY-----";
}
function encrypt(array $payload, string $pemKey): string
{
$keyEncryption = new AlgorithmManager([new RSAOAEP256()]);
$contentEncryption = new AlgorithmManager([new A256GCM()]);
$compressionManager = new CompressionMethodManager([]); // no compression
$jwk = JWKFactory::createFromKey($pemKey, null, ['use' => 'enc']);
$jweBuilder = new JWEBuilder($keyEncryption, $contentEncryption, $compressionManager);
$jwe = $jweBuilder
->create()
->withPayload(json_encode($payload, JSON_THROW_ON_ERROR))
->withSharedProtectedHeader([
'alg' => 'RSA-OAEP-256',
'enc' => 'A256GCM',
])
->addRecipient($jwk)
->build();
$serializer = new CompactSerializer();
return $serializer->serialize($jwe, 0);
}
function main(array $cardInfo): array
{
$publicKey = getPublicKey();
$encrypted = encrypt($cardInfo, $publicKey);
echo "JWE: {$encrypted}\n";
$token = tokenize($encrypted);
echo "Token: " . json_encode($token, JSON_PRETTY_PRINT) . "\n";
return $token;
}
main($cardInfo);
Usa esta opción solo si no puedes cifrar la información. Es equivalente al flujo cifrado, pero aquí envías los campos de la tarjeta en claro en el cuerpo en lugar de un payload cifrado.
Para tokenizar una tarjeta debes usar el siguiente endpoint:
POST /v1/tokens/cards
Usa la llave pública de tu comercio en el header de autenticación.
A este endpoint, debes enviar la información de la tarjeta:
{
"number": "4242424242424242", // Número de la tarjeta
"cvc": "123", // Código de seguridad de la tarjeta (3 o 4 dígitos según corresponda)
"exp_month": "08", // Mes de expiración (string de 2 dígitos)
"exp_year": "28", // Año expresado current 2 dígitos
"card_holder": "José Pérez" // Nombre del tarjetahabiente
}
A lo que el endpoint responderá:
{
"status": "CREATED",
"data": {
"id": "tok_prod_1_BBb749EAB32e97a2D058Dd538a608301", // TOKEN que debe ser usado para crear la transacción
"created_at": "2020-01-02T18:52:35.850+00:00",
"brand": "VISA",
"name": "VISA-4242",
"last_four": "4242",
"bin": "424242",
"exp_year": "28",
"exp_month": "08",
"card_holder": "José Pérez",
"expires_at": "2020-06-30T18:52:35.000Z"
}
}
De esta respuesta, el valor del campo "id" es el token que debes usar dentro del método de pago (en este caso "tok_prod_1_BBb749EAB32e97a2D058Dd538a608301"), para posteriormente crear una transacción.
Si necesitas hacer múltiples cobros a una misma tarjeta, utiliza Fuentes de Pago.
Realiza la transacción
Teniendo estos detalles, y habiéndole preguntado al usuario final el número de cuotas ("installments") en los que desea pagar, los campos de método de pago de una nueva transacción con tarjeta de crédito o débito deben ser similares a los siguientes:
{
"payment_method": {
"type": "CARD",
"installments": 1, // Número de cuotas
"token": "tok_prod_1_BBb749EAB32e97a2D058Dd538a608301" // Token de la tarjeta de crédito
}
// Otros campos de la transacción a crear...
}
Por último, recuerda siempre verificar periódicamente el status de la transacción en Wompi desde tu sistema usando el ID de transacción y nuestro API con el endpoint GET /v1/transactions/<ID_TRANSACCION>.
Clave
Te guiáremos en el proceso de utilizar el medio de pago Clave en nuestra API. Clave es un sistema de tarjetas perteneciente a la compañía Telered en Panamá que ofrece a tus clientes una forma segura de procesar pagos en tu plataforma.
Crear la transacción
Debes crear una nueva transacción utilizando el endpoint POST /v1/transactions. Asegúrate de incluir los siguientes campos específicos para el método de pago Clave:
{
"payment_method": {
"type": "CLAVE"
},
// Otros campos de la transacción a crear...
"amount_in_cents": 2000,
"currency": "USD",
"customer_email": "{{EMAIL}}",
"reference": "{{REFERENCE}}",
"acceptance_token": "{{ACCEPTANCE_TOKEN}}"
}
Consultar la transacción
Después de crear la transacción, es esencial verificar periódicamente los cambios en la transacción a través del API de Wompi utilizando el ID de transacción. Puedes hacer esto utilizando el endpoint GET /v1/transactions/<ID_TRANSACCION>.
Una vez obtenida la respuesta, debes validar el campo data->payment_method->extra->clave_auth->url. Esta URL deberá ser cargada en un elemento <iframe> dentro de tu página de pago. Aquí, tus clientes interactuarán con el sistema Clave para continuar el proceso de la transacción. Al finalizar exitosamente el proceso, el campo data->status cambiará a APPROVED, DECLINED o ERROR según corresponda.
Aquí tienes un ejemplo de la estructura de la respuesta:
{
"data": {
"id": "1156-1689191638-98479",
"created_at": "2023-07-12T19:53:58.190Z",
"amount_in_cents": 1000,
"reference": "refence_test1",
"currency": "USD",
"payment_method_type": "CLAVE",
"payment_method": {
"type": "CLAVE",
"extra": {
"clave_auth": {
"url": "URL_TEST",
"session_id": "12345",
"client_session_id": "12345"
}
}
},
"redirect_url": null,
"status": "PENDING",
"status_message": null,
"merchant": {
"name": "Comercio De Prueba",
"legal_name": "Comercio De Prueba",
"contact_name": "Pepito Perez",
"phone_number": "+507123456789",
"logo_url": null,
"legal_id_type": "RUC",
"email": "test@wompi.com",
"legal_id": "123456789-1"
},
"taxes": []
},
"meta": {}
}
