HSP Wallet Händler-Zahlungs-API
Händler-Zahlungs-API

HSP-Zahlungs-API — von einer anderen Website kassieren

Mit dieser API kassiert eine Händlerwebsite Euro auf ein HSP-Konto. Der Kunde wird auf eine HSP-Seite geleitet, gibt Telefon und PIN der HSP-Wallet-App ein und kehrt zu Ihrer Website zurück. Die PIN geht nie an Ihren Server.

Basis-URL https://comptehsp.comEURHMAC-SHA256
  1. 1Schlüssel im Bereich
  2. 2Zahlungslink
  3. 3Signierter Webhook
Händlerbereich

1. API-Schlüssel holen (einer pro Händler)

1. Händlerbereich öffnen: https://comptehsp.com/commercant (Telefon + PIN der App).
2. Karte « Website-Zahlungs-API » öffnen.
3. « Schlüssel holen » wählen. HSP zeigt:
- key_id (öffentlich), beginnt mit hspk_
- secret (geheim), beginnt mit hsps_
4. Das Geheimnis sofort kopieren. Es wird nie wieder angezeigt. « Neu erzeugen » erstellt ein neues Paar und entwertet das alte.
5. « Widerrufen » schaltet die API ab.
Doku: /pay/checkout/docs?lang=fr|en|es|de|pt|ar

2. HMAC-Signatur bei jedem Aufruf

Jeder Händleraufruf sendet:
- X-HSP-Key: key_id
- X-HSP-Timestamp: Unix-Zeit in Sekunden
- X-HSP-Signature: HMAC-SHA256 hex

Signierte Nachricht, vier Zeilen mit \n:
timestamp
METHOD
path
sha256_hex(body)

Basis: https://comptehsp.com

3. Zahlung anlegen

POST /api/v1/checkout/payments

FeldPflichtBeschreibung
referencejaIhre Bestellnummer, eindeutig. 1–80 Zeichen: Buchstaben, Ziffern, . _ : -
amountjaBetrag in EUR, 0,01 bis 50000. JSON-Zahl.
currencyjaNur EUR
return_urljaÖffentliche https-URL nach der Zahlung
cancel_urlneinÖffentliche https-URL bei Abbruch
webhook_urlneinÖffentliche https-URL für das Ergebnis
descriptionneinText für den Kunden, max. 180 Zeichen
expires_inneinLebensdauer in Sekunden, 60 bis 86400. Standard 1800
localeneinfr, en, es, de, pt, ar
customer.emailneinFür Ihre Unterlagen
customer.nameneinFreier Name
metadataneinFreies JSON-Objekt, max. 2000 Zeichen

Die Antwort 201 enthält checkout_url. Leiten Sie den Kunden dorthin. HSP kehrt zu return_url zurück mit hsp_payment_id, hsp_reference und hsp_status. Bestätigen Sie immer über den Status oder den signierten Webhook.

4. Doppelschutz, Ablauf, Status

Eine Referenz wird nur einmal belastet.
- Gültiges PENDING: derselbe Link, keine zweite Zahlung.
- Abgelaufenes PENDING, CANCELLED oder FAILED ohne Belastung: die Referenz wird mit einem neuen Link wieder geöffnet.
- PAID oder Belastung läuft: HTTP 409, already_paid.
- Zwei gleichzeitige Bestätigungen belasten nur einmal.
- Nach Ablauf: EXPIRED, HTTP 410 payment_expired.
- Fünf falsche PINs sperren den Link. Fünf falsche PINs sperren das Konto für 15 Minuten.

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

5. Status prüfen

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

Dieselbe HMAC-Signatur, leerer Body. Die Bestellung ist bezahlt nur wenn status PAID ist.

6. Webhook

HSP sendet POST-JSON an webhook_url bei PAID, EXPIRED, CANCELLED oder FAILED. Bis zu 6 Versuche, solange Sie nicht 2xx antworten.

Header: X-HSP-Event, X-HSP-Timestamp, X-HSP-Signature.
Die Signatur ist HMAC-SHA256 von timestamp + Zeilenumbruch + empfangenem Roh-Body.
Ereignisse: checkout.payment.paid, .expired, .cancelled, .failed.
payment enthält id, reference, status, amount, currency, paid_at, payer_label, ledger_ref, metadata.
Signatur gegen den Roh-Body prüfen. Zeitstempel außerhalb von 5 Minuten ablehnen. Erst ausliefern, wenn reference, amount und status PAID stimmen.

7. Fehlercodes

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.

Beispiele

Für Node.js, PHP und Python: HSP_KEY (hspk_…) und HSP_SECRET (hsps_…). In WordPress unter Einstellungen → HSP Paiement einfügen.

Zahlung anlegen

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

Status prüfen

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

Webhook prüfen

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