Passer au contenu principal
Suivez ces bonnes pratiques pour construire des intégrations robustes, sécurisées et fiables avec l’API Chariow, en particulier lors de la gestion des flux de paiement.

Bonnes pratiques de l’API Checkout

Toujours gérer tous les états de réponse

L’API checkout renvoie différents états qui nécessitent différents traitements. Ne supposez jamais qu’un checkout nécessitera toujours un paiement.
async function handleCheckout(checkoutData) {
  const response = await fetch('https://api.chariow.com/v1/checkout', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(checkoutData)
  });

  const result = await response.json();

  switch (result.data.step) {
    case 'awaiting_payment':
      // Redirection vers le paiement - cas le plus courant
      return { action: 'redirect', url: result.data.payment.checkout_url };

    case 'completed':
      // Produit gratuit - vente finalisée immédiatement
      return { action: 'success', saleId: result.data.purchase.id };

    case 'already_purchased':
      // Le client possède déjà ce produit
      return { action: 'already_owned', message: result.data.message };

    default:
      // Gérer les états inattendus gracieusement
      console.error('État de checkout inattendu :', result.data.step);
      return { action: 'error', message: 'Réponse inattendue du système de paiement' };
  }
}

Valider les données avant soumission

Réduisez les échecs de paiement en validant les données client de votre côté avant d’appeler l’API.
function validateCheckoutData(data) {
  const errors = [];

  // Validation de l'email
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!data.email || !emailRegex.test(data.email)) {
    errors.push('Un email valide est requis');
  }

  // Validation du nom
  if (!data.first_name || data.first_name.length > 50) {
    errors.push('Le prénom est requis (max 50 caractères)');
  }
  if (!data.last_name || data.last_name.length > 50) {
    errors.push('Le nom est requis (max 50 caractères)');
  }

  // Validation du téléphone - numérique uniquement
  const phoneNumber = data.phone?.number?.replace(/\D/g, '');
  if (!phoneNumber || phoneNumber.length < 6) {
    errors.push('Un numéro de téléphone valide est requis');
  }

  // Validation du code pays
  const validCountryCodes = ['US', 'CA', 'GB', 'FR', 'DE', 'ES', 'IT', /* ... */];
  if (!data.phone?.country_code || !validCountryCodes.includes(data.phone.country_code)) {
    errors.push('Un code pays valide est requis');
  }

  return {
    valid: errors.length === 0,
    errors
  };
}

Stocker les IDs de vente immédiatement

Persistez toujours l’ID de vente dès que vous le recevez. C’est crucial pour le support client, les remboursements et le suivi des commandes.
async function processCheckout(checkoutData, orderId) {
  const result = await initiateCheckout(checkoutData);

  if (result.data.purchase?.id) {
    // Stocker immédiatement - avant toute redirection
    await database.orders.update(orderId, {
      chariow_sale_id: result.data.purchase.id,
      checkout_initiated_at: new Date()
    });
  }

  return result;
}

Utiliser les Pulses pour une confirmation de vente fiable

Ne vous fiez jamais uniquement aux URL de redirection pour confirmer les achats. Les pages de paiement peuvent être fermées, les redirections peuvent échouer et les clients peuvent ne pas revenir sur votre site.
La redirect_url est uniquement pour l’expérience utilisateur. Utilisez toujours les Pulses (webhooks) pour une confirmation de vente fiable.
// Configurer un endpoint Pulse pour la confirmation de vente
app.post('/webhooks/chariow', async (req, res) => {
  const signature = req.headers['x-chariow-signature'];

  // Vérifier la signature du webhook
  if (!verifySignature(req.body, signature)) {
    return res.status(401).send('Signature invalide');
  }

  const { event, data } = req.body;

  switch (event) {
    case 'sale.completed':
      await fulfillOrder(data.id);
      await sendConfirmationEmail(data.customer.email);
      break;

    case 'sale.refunded':
      await revokeAccess(data.id);
      await sendRefundEmail(data.customer.email);
      break;
  }

  res.status(200).send('OK');
});

Bonnes pratiques de sécurité

Ne jamais exposer les clés API dans le code client

Les clés API ne doivent être utilisées que côté serveur. Ne les incluez jamais dans les bundles JavaScript, les applications mobiles ou tout code exposé au client.
// Côté serveur (Node.js)
const API_KEY = process.env.CHARIOW_API_KEY;

app.post('/api/checkout', async (req, res) => {
  const response = await fetch('https://api.chariow.com/v1/checkout', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(req.body)
  });

  res.json(await response.json());
});

Valider les signatures des webhooks

Vérifiez toujours que les webhooks entrants proviennent réellement de Chariow en vérifiant la signature.
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Utiliser les variables d’environnement

Stockez toute configuration sensible dans les variables d’environnement, jamais dans le code.
# Fichier .env (ne jamais commiter)
CHARIOW_API_KEY=sk_live_your_api_key
CHARIOW_WEBHOOK_SECRET=whsec_your_webhook_secret
// Charger depuis l'environnement
const config = {
  apiKey: process.env.CHARIOW_API_KEY,
  webhookSecret: process.env.CHARIOW_WEBHOOK_SECRET
};

Bonnes pratiques de gestion des erreurs

Implémenter une gestion d’erreurs complète

Gérez tous les scénarios d’erreur possibles gracieusement pour offrir une bonne expérience utilisateur.
async function safeCheckout(checkoutData) {
  try {
    const response = await fetch('https://api.chariow.com/v1/checkout', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(checkoutData)
    });

    // Gérer les erreurs HTTP
    if (!response.ok) {
      const error = await response.json();

      switch (response.status) {
        case 401:
          console.error('Clé API invalide');
          return { error: 'Erreur de configuration du système de paiement' };

        case 404:
          return { error: 'Produit non trouvé ou indisponible' };

        case 422:
          // Erreurs de validation - retourner les messages spécifiques aux champs
          return { error: 'Échec de validation', fields: error.errors };

        case 429:
          return { error: 'Trop de requêtes. Veuillez réessayer dans un moment.' };

        default:
          return { error: 'Système de paiement indisponible. Veuillez réessayer.' };
      }
    }

    return await response.json();

  } catch (networkError) {
    // Gérer les échecs réseau
    console.error('Erreur réseau :', networkError);
    return { error: 'Impossible de se connecter au système de paiement. Vérifiez votre connexion.' };
  }
}

Afficher des messages d’erreur conviviaux

Mappez les erreurs API vers des messages utiles qui guident les utilisateurs pour corriger les problèmes.
const errorMessages = {
  'email': 'Veuillez entrer une adresse email valide',
  'phone.number': 'Veuillez entrer un numéro de téléphone valide (chiffres uniquement)',
  'phone.country_code': 'Veuillez sélectionner votre pays',
  'address': 'Veuillez entrer votre adresse de livraison',
  'city': 'Veuillez entrer votre ville',
  'state': 'Veuillez entrer votre état ou région',
  'country': 'Veuillez sélectionner votre pays',
  'zip': 'Veuillez entrer votre code postal',
  'discount_code': 'Ce code de réduction est invalide ou a expiré'
};

function getFieldError(fieldName, apiErrors) {
  if (apiErrors[fieldName]) {
    return errorMessages[fieldName] || apiErrors[fieldName][0];
  }
  return null;
}

Journaliser les erreurs pour le débogage

Maintenez des logs détaillés pour le dépannage tout en gardant les données sensibles sécurisées.
function logCheckoutError(error, checkoutData) {
  // Supprimer les données sensibles avant la journalisation
  const safeData = {
    product_id: checkoutData.product_id,
    email: maskEmail(checkoutData.email),
    timestamp: new Date().toISOString(),
    error: error.message || error
  };

  console.error('Échec du checkout :', JSON.stringify(safeData));

  // Envoyer au service de monitoring
  monitoring.captureError(error, { context: safeData });
}

function maskEmail(email) {
  const [local, domain] = email.split('@');
  return `${local.slice(0, 2)}***@${domain}`;
}

Bonnes pratiques de performance

Implémenter des délais d’expiration

Ne laissez pas les appels API se bloquer indéfiniment. Définissez des délais raisonnables.
async function checkoutWithTimeout(checkoutData, timeoutMs = 30000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch('https://api.chariow.com/v1/checkout', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(checkoutData),
      signal: controller.signal
    });

    return await response.json();
  } finally {
    clearTimeout(timeout);
  }
}

Mettre en cache les données produit

Réduisez les appels API en mettant en cache les informations produit qui ne changent pas fréquemment.
const productCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function getProduct(productId) {
  const cached = productCache.get(productId);

  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  const response = await fetch(`https://api.chariow.com/v1/products/${productId}`, {
    headers: { 'Authorization': `Bearer ${API_KEY}` }
  });

  const data = await response.json();

  productCache.set(productId, {
    data: data.data,
    timestamp: Date.now()
  });

  return data.data;
}

Respecter les limites de débit

Gérez la limitation de débit gracieusement avec un backoff exponentiel.
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
      console.log(`Limite de débit atteinte. Nouvelle tentative après ${retryAfter}s`);
      await sleep(retryAfter * 1000);
      continue;
    }

    return response;
  }

  throw new Error('Nombre maximum de tentatives dépassé');
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Bonnes pratiques de test

Tester tous les scénarios de checkout

Créez des tests complets pour chaque résultat de checkout possible.
describe('Intégration Checkout', () => {
  test('gère le checkout de produit payant', async () => {
    const result = await handleCheckout({
      product_id: 'prd_paid_product',
      email: '[email protected]',
      // ...
    });

    expect(result.action).toBe('redirect');
    expect(result.url).toContain('payment.chariow.com');
  });

  test('gère le checkout de produit gratuit', async () => {
    const result = await handleCheckout({
      product_id: 'prd_free_product',
      email: '[email protected]',
      // ...
    });

    expect(result.action).toBe('success');
    expect(result.saleId).toBeDefined();
  });

  test('gère le produit déjà acheté', async () => {
    const result = await handleCheckout({
      product_id: 'prd_already_owned',
      email: '[email protected]',
      // ...
    });

    expect(result.action).toBe('already_owned');
  });

  test('gère gracieusement un produit invalide', async () => {
    const result = await handleCheckout({
      product_id: 'prd_nonexistent',
      email: '[email protected]',
      // ...
    });

    expect(result.error).toBeDefined();
  });

  test('gère les produits nécessitant une livraison', async () => {
    const result = await handleCheckout({
      product_id: 'prd_physical',
      email: '[email protected]',
      address: '123 Rue Principale',
      city: 'Paris',
      state: 'Île-de-France',
      country: 'FR',
      zip: '75001',
      // ...
    });

    expect(result.action).toBe('redirect');
  });
});

Utiliser l’environnement de staging pour les tests d’intégration

Testez toujours contre un environnement de staging avant de déployer en production.
const config = {
  development: {
    apiUrl: 'https://api.staging.chariow.com/v1',
    apiKey: process.env.CHARIOW_STAGING_KEY
  },
  production: {
    apiUrl: 'https://api.chariow.com/v1',
    apiKey: process.env.CHARIOW_LIVE_KEY
  }
};

const env = process.env.NODE_ENV || 'development';
const apiConfig = config[env];

Checklist pour la production

Avant la mise en production, assurez-vous d’avoir complété cette checklist :
  • Clés API stockées dans les variables d’environnement
  • Appels API effectués côté serveur uniquement
  • Signatures des webhooks vérifiées
  • HTTPS utilisé pour tous les endpoints
  • Données sensibles non journalisées
  • Tous les codes de statut HTTP gérés
  • Erreurs réseau interceptées
  • Messages d’erreur conviviaux affichés
  • Erreurs journalisées pour le débogage
  • Limitation de débit gérée gracieusement
  • Tous les états de checkout gérés (awaiting_payment, completed, already_purchased)
  • IDs de vente stockés immédiatement
  • Pulses configurés pour la confirmation de vente
  • Données client validées avant soumission
  • Champs de livraison inclus si requis
  • Tests unitaires pour la logique de checkout
  • Tests d’intégration avec l’API de staging
  • Tous les types de produits testés
  • Scénarios d’erreur testés
  • Tests de charge effectués

Prochaines étapes