HSP Wallet API de pago comercio
API de pago comercio

API de pago HSP — cobrar desde otro sitio

Esta API permite a un sitio comercio cobrar euros en una cuenta HSP. El cliente es redirigido a una página HSP, introduce el teléfono y el PIN de la aplicación HSP Wallet y vuelve a su sitio. El PIN nunca se envía a su servidor.

URL base https://comptehsp.comEURHMAC-SHA256
  1. 1Clave en el espacio
  2. 2Enlace de pago
  3. 3Webhook firmado
Espacio comercio

1. Obtener la clave API (una por comercio)

1. Entre en el espacio comercio: https://comptehsp.com/commercant (teléfono + PIN de la aplicación).
2. Abra la tarjeta « API de pago web ».
3. Pulse « Obtener la clave ». HSP muestra:
- key_id (público), empieza por hspk_
- secret (privado), empieza por hsps_
4. Copie el secreto de inmediato. No se vuelve a mostrar. « Regenerar » crea un par nuevo e invalida el anterior.
5. « Revocar » desactiva la API.
Documentación: /pay/checkout/docs?lang=fr|en|es|de|pt|ar

2. Firma HMAC en cada llamada

Cada petición del comercio envía:
- X-HSP-Key: key_id
- X-HSP-Timestamp: tiempo Unix en segundos
- X-HSP-Signature: HMAC-SHA256 en hexadecimal

Mensaje firmado, cuatro líneas separadas por \n:
timestamp
METHOD
path
sha256_hex(cuerpo)

Base: https://comptehsp.com

3. Crear un pago

POST /api/v1/checkout/payments

CampoObligatorioDescripción
referencesíSu pedido, único. 1 a 80 caracteres: letras, cifras, . _ : -
amountsíImporte en EUR, de 0,01 a 50000. Número JSON.
currencysíSolo EUR
return_urlsíURL https pública de retorno tras el pago
cancel_urlnoURL https pública si el cliente cancela
webhook_urlnoURL https pública que recibe el resultado
descriptionnoTexto mostrado al cliente, 180 caracteres
expires_innoVida del enlace en segundos, 60 a 86400. Defecto: 1800
localenofr, en, es, de, pt, ar
customer.emailnoGuardado para su seguimiento
customer.namenoNombre libre
metadatanoObjeto JSON, 2000 caracteres máx.

La respuesta 201 incluye checkout_url. Redirija al cliente allí. Después HSP vuelve a return_url con hsp_payment_id, hsp_reference y hsp_status. Confirme siempre con el estado o el webhook firmado.

4. Anti-duplicado, caducidad, estados

Una referencia solo puede cobrarse una vez.
- PENDING vigente: se devuelve el mismo enlace.
- PENDING caducado, CANCELLED o FAILED sin cargo: la referencia se reabre con un enlace nuevo.
- PAID o cargo en curso: HTTP 409, already_paid.
- Dos confirmaciones a la vez solo producen un cargo.
- Caducado: estado EXPIRED, HTTP 410 payment_expired.
- Cinco PIN incorrectos bloquean el enlace. Cinco PIN incorrectos en la cuenta la bloquean 15 minutos.

Estados: PENDING, PROCESSING, PAID, EXPIRED, CANCELLED, FAILED.

5. Consultar el estado

GET /api/v1/checkout/payments/{id}
GET /api/v1/checkout/payments/reference/{reference}

Misma firma HMAC y cuerpo vacío. El pedido está pagado solo si status es PAID.

6. Webhook

HSP hace POST JSON a webhook_url cuando el pago pasa a PAID, EXPIRED, CANCELLED o FAILED. Hasta 6 intentos si usted no responde 2xx.

Cabeceras: X-HSP-Event, X-HSP-Timestamp, X-HSP-Signature.
La firma es HMAC-SHA256 de timestamp + salto de línea + cuerpo bruto recibido.
Eventos: checkout.payment.paid, .expired, .cancelled, .failed.
El objeto payment incluye id, reference, status, amount, currency, paid_at, payer_label, ledger_ref y metadata.
Compare la firma con el cuerpo bruto. Rechace un reloj fuera de 5 minutos. Entregue el pedido solo si reference, amount y status PAID coinciden.

7. Códigos de error

missing_signature 401, invalid_signature 401, stale_timestamp 401, replayed_signature 401, unknown_api_key 401, revoked_api_key 401, already_paid 409, reference_conflict 409, payment_expired 410, invalid_amount 400, invalid_currency 400, invalid_url 400, invalid_pin 400, insufficient_balance 400, too_many_attempts 429.

Ejemplos

Para Node.js, PHP y Python: HSP_KEY (hspk_…) y HSP_SECRET (hsps_…). En WordPress, péguelos en Ajustes → HSP Paiement.

Crear un pago

const crypto = require('crypto');

function canonical(value) {
  if (value === null || typeof value !== 'object') return JSON.stringify(value);
  if (Array.isArray(value)) return '[' + value.map(canonical).join(',') + ']';
  const keys = Object.keys(value).filter((k) => value[k] !== undefined).sort();
  return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonical(value[k])).join(',') + '}';
}

function sign({ secret, timestamp, method, path, body }) {
  const payload = body == null ? '' : canonical(body);
  const message = [timestamp, method.toUpperCase(), path, crypto.createHash('sha256').update(payload).digest('hex')].join('\n');
  return crypto.createHmac('sha256', secret).update(message).digest('hex');
}

const body = {
  reference: 'ORDER-' + Date.now(),
  amount: 1,
  currency: 'EUR',
  description: 'Commande de test',
  return_url: 'https://shop.example/hsp/return',
  cancel_url: 'https://shop.example/hsp/cancel',
  webhook_url: 'https://shop.example/hsp/webhook',
  expires_in: 600,
  locale: 'fr',
  metadata: { cart: '1001' }
};
const timestamp = String(Math.floor(Date.now() / 1000));
const path = '/api/v1/checkout/payments';
const signature = sign({ secret: process.env.HSP_SECRET, timestamp, method: 'POST', path, body });

fetch('https://comptehsp.com' + path, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-HSP-Key': process.env.HSP_KEY,
    'X-HSP-Timestamp': timestamp,
    'X-HSP-Signature': signature
  },
  body: JSON.stringify(body)
}).then(async (res) => {
  const payment = await res.json();
  console.log(res.status, payment.checkout_url || payment);
});

Comprobar el estado

const reference = 'ORDER-1001';
const path = '/api/v1/checkout/payments/reference/' + encodeURIComponent(reference);
const timestamp = String(Math.floor(Date.now() / 1000));
const signature = sign({ secret: process.env.HSP_SECRET, timestamp, method: 'GET', path, body: null });

fetch('https://comptehsp.com' + path, {
  headers: {
    'X-HSP-Key': process.env.HSP_KEY,
    'X-HSP-Timestamp': timestamp,
    'X-HSP-Signature': signature
  }
}).then(async (res) => console.log(res.status, await res.json()));

Verificar el webhook

const crypto = require('crypto');

function verifyWebhook(secret, rawBody, timestamp, signature) {
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(timestamp)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(timestamp + '\n' + rawBody).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(String(signature || ''), 'hex');
  return a.length === b.length && a.length === 32 && crypto.timingSafeEqual(a, b);
}

// Express : app.use(express.raw({ type: 'application/json' })) sur cette route
app.post('/hsp/webhook', (req, res) => {
  const raw = req.body.toString('utf8');
  const ok = verifyWebhook(process.env.HSP_SECRET, raw, req.get('x-hsp-timestamp'), req.get('x-hsp-signature'));
  if (!ok) return res.status(401).end();
  const event = JSON.parse(raw);
  if (event.payment.status === 'PAID' && event.payment.amount === expectedAmount) {
    // livrer la commande une seule fois (idempotence sur payment.reference)
  }
  res.status(200).json({ received: true });
});