Guide technique complet de l'intégration Shopify-CRM que j'ai développée. Webhooks JSON, synchronisation temps réel et gestion des erreurs. Code Python inclus.
Client : Boutique e-commerce (Marseille)
Problème : Saisie manuelle des commandes Shopify dans le CRM
Solution : Webhooks automatiques + API REST
# Structure du projet
shopify-crm-integration/
├── app.py # Application Flask principale
├── webhooks.py # Gestion des webhooks Shopify
├── crm_client.py # Client API pour le CRM
├── models.py # Modèles de données
├── config.py # Configuration
└── requirements.txt # Dépendances Python
Cet article contient du code Python complet pour l'intégration Shopify-CRM, des techniques de webhooks et de gestion d'erreurs.
# Configuration des webhooks dans Shopify Admin
webhooks = [
{
"topic": "orders/create",
"address": "https://votre-domaine.com/webhook/order-created",
"format": "json"
},
{
"topic": "orders/updated",
"address": "https://votre-domaine.com/webhook/order-updated",
"format": "json"
},
{
"topic": "orders/paid",
"address": "https://votre-domaine.com/webhook/order-paid",
"format": "json"
}
]
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
from crm_client import CRMClient
app = Flask(__name__)
@app.route('/webhook/order-created', methods=['POST'])
def handle_order_created():
# Vérification de l'authenticité du webhook
if not verify_webhook(request):
return jsonify({'error': 'Unauthorized'}), 401
try:
order_data = request.get_json()
# Traitement de la commande
processed_order = process_shopify_order(order_data)
# Envoi vers le CRM
crm_client = CRMClient()
result = crm_client.create_order(processed_order)
if result['success']:
return jsonify({'status': 'success', 'crm_id': result['id']})
else:
return jsonify({'error': result['error']}), 500
except Exception as e:
return jsonify({'error': str(e)}), 500
def verify_webhook(request):
"""Vérification de l'authenticité du webhook Shopify"""
hmac_header = request.headers.get('X-Shopify-Hmac-Sha256')
calculated_hmac = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
request.get_data(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(calculated_hmac, hmac_header)
def process_shopify_order(shopify_order):
"""Transforme une commande Shopify en format CRM"""
# Extraction des informations client
customer = shopify_order.get('customer', {})
billing_address = shopify_order.get('billing_address', {})
# Calcul du total
total_price = float(shopify_order['total_price'])
tax_amount = float(shopify_order.get('total_tax', 0))
# Formatage pour le CRM
crm_order = {
'external_id': shopify_order['id'],
'order_number': shopify_order['name'],
'customer': {
'email': customer.get('email'),
'first_name': customer.get('first_name'),
'last_name': customer.get('last_name'),
'phone': customer.get('phone'),
'address': {
'street': billing_address.get('address1'),
'city': billing_address.get('city'),
'zip_code': billing_address.get('zip'),
'country': billing_address.get('country')
}
},
'items': [],
'totals': {
'subtotal': total_price - tax_amount,
'tax': tax_amount,
'total': total_price
},
'status': map_order_status(shopify_order['fulfillment_status']),
'created_at': shopify_order['created_at']
}
# Traitement des articles
for line_item in shopify_order.get('line_items', []):
crm_order['items'].append({
'product_id': line_item['product_id'],
'variant_id': line_item['variant_id'],
'title': line_item['title'],
'quantity': line_item['quantity'],
'price': float(line_item['price'])
})
return crm_order
def map_order_status(shopify_status):
"""Mappe les statuts Shopify vers le CRM"""
status_mapping = {
None: 'pending',
'fulfilled': 'completed',
'partial': 'processing',
'restocked': 'cancelled'
}
return status_mapping.get(shopify_status, 'pending')
import requests
import json
from datetime import datetime
class CRMClient:
def __init__(self):
self.base_url = CRM_API_URL
self.api_key = CRM_API_KEY
self.headers = {
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json'
}
def create_order(self, order_data):
"""Crée une commande dans le CRM"""
try:
response = requests.post(
f"{self.base_url}/api/orders",
headers=self.headers,
json=order_data,
timeout=30
)
if response.status_code == 201:
return {
'success': True,
'id': response.json().get('id'),
'message': 'Commande créée avec succès'
}
else:
return {
'success': False,
'error': f'Erreur CRM: {response.status_code} - {response.text}'
}
except requests.exceptions.Timeout:
return {
'success': False,
'error': 'Timeout - Le CRM ne répond pas'
}
except Exception as e:
return {
'success': False,
'error': f'Erreur de connexion: {str(e)}'
}
def update_order(self, order_id, update_data):
"""Met à jour une commande existante"""
try:
response = requests.put(
f"{self.base_url}/api/orders/{order_id}",
headers=self.headers,
json=update_data,
timeout=30
)
return response.status_code == 200
except Exception as e:
print(f"Erreur mise à jour commande: {e}")
return False
import time
from functools import wraps
def retry_on_failure(max_retries=3, delay=1):
"""Décorateur pour retry automatique"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
result = func(*args, **kwargs)
if result.get('success'):
return result
except Exception as e:
if attempt == max_retries - 1:
return {
'success': False,
'error': f'Échec après {max_retries} tentatives: {str(e)}'
}
time.sleep(delay * (2 ** attempt)) # Backoff exponentiel
return {'success': False, 'error': 'Max retries atteint'}
return wrapper
return decorator
@retry_on_failure(max_retries=3, delay=2)
def sync_order_to_crm(order_data):
"""Synchronise une commande avec retry automatique"""
crm_client = CRMClient()
return crm_client.create_order(order_data)
import logging
from datetime import datetime
# Configuration des logs
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('shopify_crm.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def log_webhook_event(event_type, order_id, status, details=None):
"""Log des événements webhook"""
log_data = {
'timestamp': datetime.now().isoformat(),
'event_type': event_type,
'order_id': order_id,
'status': status,
'details': details
}
logger.info(f"Webhook {event_type}: {json.dumps(log_data)}")
# Utilisation dans les webhooks
@app.route('/webhook/order-created', methods=['POST'])
def handle_order_created():
order_data = request.get_json()
order_id = order_data.get('id')
try:
# ... traitement ...
log_webhook_event('order_created', order_id, 'success')
return jsonify({'status': 'success'})
except Exception as e:
log_webhook_event('order_created', order_id, 'error', str(e))
return jsonify({'error': str(e)}), 500
Chaque intégration a ses spécificités. Contactez-moi pour une solution sur-mesure adaptée à votre stack technique.
Discuter de votre projet