HSP Wallet API de pagamento
API de pagamento

API de pagamento HSP — cobrar a partir de outro site

Esta API permite a um site comerciante receber euros numa conta HSP. O cliente é redirecionado para uma página HSP, introduz o telefone e o PIN da aplicação HSP Wallet e volta ao seu site. O PIN nunca é enviado ao seu servidor.

URL base https://comptehsp.comEURHMAC-SHA256
  1. 1Chave no espaço
  2. 2Link de pagamento
  3. 3Webhook assinado
Espaço comerciante

1. Obter a chave API (uma por comerciante)

1. Entre no espaço comerciante: https://comptehsp.com/commercant (telefone + PIN da aplicação).
2. Abra o cartão « API de pagamento web ».
3. Clique em « Obter a chave ». A HSP mostra:
- key_id (público), começa por hspk_
- secret (privado), começa por hsps_
4. Copie o segredo de imediato. Não volta a ser mostrado. « Regenerar » cria um par novo e invalida o anterior.
5. « Revogar » desliga a API.
Documentação: /pay/checkout/docs?lang=fr|en|es|de|pt|ar

2. Assinatura HMAC em cada chamada

Cada pedido do comerciante envia:
- X-HSP-Key: key_id
- X-HSP-Timestamp: tempo Unix em segundos
- X-HSP-Signature: HMAC-SHA256 em hexadecimal

Mensagem assinada, quatro linhas separadas por \n:
timestamp
METHOD
path
sha256_hex(corpo)

Base: https://comptehsp.com

3. Criar um pagamento

POST /api/v1/checkout/payments

CampoObrigatórioDescrição
referencesimA sua encomenda, única. 1 a 80 caracteres: letras, dígitos, . _ : -
amountsimMontante em EUR, de 0,01 a 50000. Número JSON.
currencysimApenas EUR
return_urlsimURL https pública de regresso após o pagamento
cancel_urlnãoURL https pública se o cliente cancelar
webhook_urlnãoURL https pública que recebe o resultado
descriptionnãoTexto mostrado ao cliente, 180 caracteres
expires_innãoVida do link em segundos, 60 a 86400. Omissão: 1800
localenãofr, en, es, de, pt, ar
customer.emailnãoGuardado para o seu acompanhamento
customer.namenãoNome livre
metadatanãoObjeto JSON, 2000 caracteres no máximo

A resposta 201 inclui checkout_url. Redirecione o cliente para lá. A HSP volta a return_url com hsp_payment_id, hsp_reference e hsp_status. Confirme sempre com o estado ou com o webhook assinado.

4. Anti-duplicação, expiração, estados

Uma referência só pode ser debitada uma vez.
- PENDING ainda válido: devolve-se o mesmo link.
- PENDING expirado, CANCELLED ou FAILED sem débito: a referência reabre com um link novo.
- PAID ou débito em curso: HTTP 409, already_paid.
- Duas confirmações em simultâneo só debitam uma vez.
- Expirado: estado EXPIRED, HTTP 410 payment_expired.
- Cinco PIN errados bloqueiam o link. Cinco PIN errados bloqueiam a conta durante 15 minutos.

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

5. Verificar o estado

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

A mesma assinatura HMAC, corpo vazio. A encomenda está paga apenas se status for PAID.

6. Webhook

A HSP faz POST JSON para webhook_url quando o pagamento passa a PAID, EXPIRED, CANCELLED ou FAILED. Até 6 tentativas se não responder 2xx.

Cabeçalhos: X-HSP-Event, X-HSP-Timestamp, X-HSP-Signature.
A assinatura é HMAC-SHA256 de timestamp + quebra de linha + corpo bruto recebido.
Eventos: checkout.payment.paid, .expired, .cancelled, .failed.
O objeto payment inclui id, reference, status, amount, currency, paid_at, payer_label, ledger_ref e metadata.
Compare a assinatura com o corpo bruto. Recuse um relógio fora de 5 minutos. Entregue a encomenda só se reference, amount e status PAID coincidirem.

7. Códigos de erro

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.

Exemplos

Para Node.js, PHP e Python: HSP_KEY (hspk_…) e HSP_SECRET (hsps_…). No WordPress, cole em Definições → HSP Paiement.

Criar um pagamento

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);
});

Verificar o 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 o 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 });
});