← Retour

API KARDS pour votre site

Créditez les points de vos clients à chaque commande en ligne, sans scanner. Réservé aux forfaits Pro et supérieurs.

Créer ma clé dans le portail →

Principe

  1. Vous créez une clé dans le portail (Intégrations). Elle n'est affichée qu'une fois.
  2. Le serveur de votre site appelle KARDS quand une commande est payée.
  3. KARDS retrouve la carte du client par l'email de la commande, la crédite et lui envoie une notification.

À respecter

  • Serveur uniquement : jamais dans le JavaScript d'une page web, la clé y serait lisible par tous.
  • Créditez quand le paiement est confirmé (webhook du prestataire), jamais depuis la page « merci ».
  • Paiement au retrait ? N'envoyez rien : scannez la carte en caisse comme d'habitude.
  • Annulez au remboursement (route /cancel), sinon le client garde les points d'une commande remboursée.
  • Aucune donnée personnelle dans la référence de commande : l'identifiant technique de la commande suffit.
  • Réglez un plafond par commande proche de votre plus gros panier réel.
  • Clé exposée ou doute : révoquez-la dans le portail, l'ancienne cesse aussitôt de fonctionner.

Créditer une commande

POST https://api.kards-fidelite.com/api/v1/integrations/credits
En-têtes Authorization: Bearer kards_live_… et Content-Type: application/json.

  • orderRef : identifiant technique unique de la commande, 1 à 64 caractères sans espace ni « @ », sans donnée personnelle. Rejouer la même commande ne crédite pas deux fois (réponse 200, duplicate: true).
  • email : email de la commande, utilisé seulement pour retrouver la carte. Seules les cartes dont l'email a été confirmé par le client (ou saisies en caisse) sont créditées. Sans carte correspondante, l'email n'est ni conservé ni journalisé et la réponse 404 contient signupUrl, le lien d'inscription à proposer au client.
  • amountCents : montant payé en centimes. KARDS calcule les points avec la règle de votre programme, comme en caisse.

Lecture : GET …/credits?orderRef=…. Annulation ou remboursement : POST …/credits/cancel avec {"orderRef": "…"}.

Exemples

curl

curl -X POST https://api.kards-fidelite.com/api/v1/integrations/credits \
  -H "Authorization: Bearer kards_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{"orderRef":"WC-1042","email":"client@exemple.fr","amountCents":2490}'

PHP (WooCommerce)

// functions.php du thème enfant ou mu-plugin. KARDS_API_KEY défini dans wp-config.php.
add_action('woocommerce_payment_complete', function (int $order_id): void {
    $order = wc_get_order($order_id);
    if (!$order) { return; }
    $res = wp_remote_post('https://api.kards-fidelite.com/api/v1/integrations/credits', [
        'headers' => ['Authorization' => 'Bearer ' . KARDS_API_KEY, 'Content-Type' => 'application/json'],
        'body'    => wp_json_encode([
            'orderRef'    => 'WC-' . $order->get_id(),
            'email'       => $order->get_billing_email(),
            'amountCents' => (int) round((float) $order->get_total() * 100),
        ]),
        'timeout' => 10,
    ]);
    $code = is_wp_error($res) ? 0 : wp_remote_retrieve_response_code($res);
    if (!in_array($code, [200, 201, 404], true)) {
        error_log('KARDS: crédit refusé (' . $code . ') ' . (is_wp_error($res) ? $res->get_error_message() : wp_remote_retrieve_body($res)));
    }
});
add_action('woocommerce_order_status_refunded', function (int $order_id): void {
    $res = wp_remote_post('https://api.kards-fidelite.com/api/v1/integrations/credits/cancel', [
        'headers' => ['Authorization' => 'Bearer ' . KARDS_API_KEY, 'Content-Type' => 'application/json'],
        'body'    => wp_json_encode(['orderRef' => 'WC-' . $order_id]),
        'timeout' => 10,
    ]);
    // Une annulation ratée laisse les points d'une commande remboursée : la journaliser.
    $code = is_wp_error($res) ? 0 : wp_remote_retrieve_response_code($res);
    if (!in_array($code, [200, 404], true)) {
        error_log('KARDS: annulation refusée (' . $code . '), à relancer pour la commande ' . $order_id);
    }
});

Node (webhook Stripe)

// Next.js — app/api/stripe-webhook/route.js (serveur uniquement).
// On crédite à la confirmation du PAIEMENT, signée par Stripe :
// jamais depuis la page « merci », que n'importe qui peut ouvrir.
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export async function POST(req) {
  const event = stripe.webhooks.constructEvent(
    await req.text(),
    req.headers.get("stripe-signature"),
    process.env.STRIPE_WEBHOOK_SECRET,
  );
  // « completed » arrive aussi pour un prélèvement SEPA ou un virement encore
  // en cours : on ne crédite qu'une session réellement payée.
  const paid =
    (event.type === "checkout.session.completed" && event.data.object.payment_status === "paid") ||
    event.type === "checkout.session.async_payment_succeeded";
  if (paid) {
    const session = event.data.object;
    const res = await fetch("https://api.kards-fidelite.com/api/v1/integrations/credits", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.KARDS_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        orderRef: session.payment_intent, // unique, 27 caractères
        email: session.customer_details.email,
        amountCents: session.amount_total, // déjà en centimes
      }),
    });
    if (!res.ok && res.status !== 404) console.error("KARDS crédit", res.status, await res.text());
  }
  // Remboursement : on reprend les points (même référence que le crédit).
  if (event.type === "charge.refunded" && event.data.object.refunded) {
    const res = await fetch("https://api.kards-fidelite.com/api/v1/integrations/credits/cancel", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.KARDS_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ orderRef: event.data.object.payment_intent }),
    });
    if (!res.ok && res.status !== 404) console.error("KARDS annulation", res.status, await res.text());
  }
  return new Response("ok");
}

Make / Zapier

Scénario 1 — commande PAYÉE → crédit
Déclencheur : « Shopify › Watch orders » ou « Wix › Watch new orders ».
⚠️ Ces déclencheurs partent à la COMMANDE, pas au paiement. Ajoutez un filtre juste après :
  Shopify : financial_status = paid        Wix : paymentStatus = PAID
  (sinon une commande « paiement au retrait » jamais récupérée créditerait des points)
Action : « HTTP › Make a request »
  URL       https://api.kards-fidelite.com/api/v1/integrations/credits
  Méthode   POST
  En-tête   Authorization: Bearer kards_live_VOTRE_CLE
  Corps     Raw / JSON
            {"orderRef": "{{order.name}}", "email": "{{order.email}}", "amountCents": {{round(order.total_price * 100)}}}
  Parse response : oui
Filtre optionnel sur le statut 404 pour envoyer un email « rejoignez le programme ».

Scénario 2 — commande remboursée ou annulée → reprise des points
Déclencheur : « Shopify › Watch orders » (filtre financial_status = refunded) ou l'événement d'annulation Wix.
Action : « HTTP › Make a request »
  URL       https://api.kards-fidelite.com/api/v1/integrations/credits/cancel
  Méthode   POST, même en-tête, corps {"orderRef": "{{order.name}}"} (même référence que le crédit).

Site généré par une IA (Lovable, Bolt, Cursor…)

Copiez ce texte dans l'outil qui a créé votre site :

Ajoute à mon site l'intégration fidélité KARDS. Règles impératives :
1. L'appel à l'API KARDS se fait UNIQUEMENT côté serveur (route API, fonction serverless, edge function, webhook), jamais dans du code exécuté par le navigateur.
2. La clé est lue depuis une variable d'environnement secrète KARDS_API_KEY. Elle n'apparaît ni dans le code, ni dans le dépôt Git, ni dans une variable publique (NEXT_PUBLIC_, VITE_, PUBLIC_…).
3. Le crédit est déclenché uniquement quand le paiement est confirmé par le prestataire de paiement (webhook signé, par exemple Stripe checkout.session.completed avec payment_status = "paid", ou checkout.session.async_payment_succeeded), jamais depuis la page de confirmation ni à la simple validation du panier. Si le client paie au retrait, ne rien envoyer : le commerçant scanne la carte en caisse.
4. Requête : POST https://api.kards-fidelite.com/api/v1/integrations/credits avec les en-têtes Authorization: Bearer <KARDS_API_KEY> et Content-Type: application/json, et le corps {"orderRef": "<identifiant unique de la commande, 64 caractères max, sans espace>", "email": "<email du client>", "amountCents": <montant payé en centimes, entier>}. La référence de commande ne contient AUCUNE donnée personnelle (pas d'email, pas de nom) : utiliser l'identifiant technique de la commande ou du paiement.
5. Réponses : 201 = crédité, 200 = déjà crédité (rejeu sans effet), 404 = le client n'a pas encore de carte confirmée (on peut lui proposer le lien signupUrl de la réponse). Toute autre réponse est journalisée côté serveur sans bloquer la commande. Ne jamais journaliser la clé ni l'en-tête Authorization.
6. Au remboursement ou à l'annulation : POST https://api.kards-fidelite.com/api/v1/integrations/credits/cancel avec {"orderRef": "<même identifiant>"}, et journaliser tout échec pour le relancer.
7. Pas de nouvelle tentative en boucle : 3 essais espacés au maximum, en cas d'erreur réseau, de 5xx, ou de 429 (attendre la durée de l'en-tête Retry-After).

Codes d'erreur

400INVALID_BODYCorps invalide ou JSON mal formé : orderRef (1-64 caractères, sans espace ni « @ »), email, amountCents entier en centimes.
401INVALID_API_KEYClé absente, mal copiée (espace en trop) ou d'un autre environnement.
401API_KEY_REVOKEDClé révoquée depuis le portail : créez-en une nouvelle.
402FEATURE_LOCKEDForfait sans l'API (Pro et supérieurs), ou programme gelé (lecture).
403BROWSER_CALL_FORBIDDENAppel depuis un navigateur : déplacez-le sur votre serveur, votre clé était visible.
403MERCHANT_FROZENProgramme gelé ou compte suspendu (MERCHANT_SUSPENDED) : écritures refusées.
404NO_CARDPas de carte à cet email confirmé : la réponse contient signupUrl pour proposer l'inscription.
404NOT_FOUNDAucun crédit pour cette référence (lecture ou annulation).
409ORDER_REF_CONFLICTRéférence déjà utilisée avec un autre email ou un autre montant.
413BODY_TOO_LARGECorps de requête au-delà de 4 Ko.
415UNSUPPORTED_MEDIA_TYPEEnvoyez le corps en Content-Type: application/json.
422AMOUNT_ABOVE_KEY_LIMITMontant au-dessus du plafond par commande de la clé (réglable dans le portail).
422CARD_DAILY_LIMITCette carte a déjà reçu 3 commandes en ligne sur 24 h : créditez en caisse si c'est légitime.
422DELTA_TOO_LARGECrédit calculé hors bornes pour votre programme.
429RATE_LIMITEDPlus de 60 écritures (300 lectures) par minute pour cette clé, ou trop de clés invalides depuis votre IP : réessayez après l'en-tête Retry-After.
500CREDIT_FAILEDAnnulation interrompue : relancez-la, elle est rejouable.

Limites

  • 60 appels par minute et par clé en écriture, 300 en lecture ; les clés invalides sont limitées par adresse IP.
  • Corps de requête limité à 4 Ko ; en cas de 429, réessayez après la durée de l'en-tête Retry-After.
  • Plafond par commande réglable par clé (500 € par défaut, 10 000 € au maximum).
  • 3 commandes en ligne par carte sur 24 heures glissantes (annulées comprises).
  • 5 clés actives par commerçant ; une clé révoquée cesse aussitôt de fonctionner.