import base64 import hashlib import hmac import logging import time import requests _logger = logging.getLogger(__name__) class WooApiClient: """WooCommerce REST API v3 client wrapper.""" def __init__(self, url, consumer_key, consumer_secret, api_version='wc/v3', timeout=30): self.base_url = url.rstrip('/') self.api_version = api_version self.timeout = timeout self.session = requests.Session() self.session.auth = (consumer_key, consumer_secret) self.session.headers.update({ 'Content-Type': 'application/json', 'User-Agent': 'FusionWooCommerce/1.0', }) def _url(self, endpoint): return f"{self.base_url}/wp-json/{self.api_version}/{endpoint}" def _request(self, method, endpoint, data=None, params=None, retries=3): url = self._url(endpoint) last_exc = None for attempt in range(retries): try: response = self.session.request( method, url, json=data, params=params, timeout=self.timeout, ) response.raise_for_status() return response.json() except Exception as exc: last_exc = exc wait = 2 ** attempt _logger.warning( "WooCommerce API %s %s failed (attempt %d/%d): %s — retrying in %ds", method, endpoint, attempt + 1, retries, exc, wait, ) if attempt < retries - 1: time.sleep(wait) raise last_exc # Convenience methods def get(self, endpoint, params=None): return self._request('GET', endpoint, params=params) def post(self, endpoint, data): return self._request('POST', endpoint, data=data) def put(self, endpoint, data): return self._request('PUT', endpoint, data=data) def delete(self, endpoint): return self._request('DELETE', endpoint) # Product endpoints def get_products(self, page=1, per_page=100, **kwargs): params = {'page': page, 'per_page': per_page, **kwargs} return self.get('products', params=params) def get_product(self, product_id): return self.get(f'products/{product_id}') def get_product_variations(self, product_id, page=1, per_page=100): params = {'page': page, 'per_page': per_page} return self.get(f'products/{product_id}/variations', params=params) def update_product(self, product_id, data): return self.put(f'products/{product_id}', data) def create_product(self, data): return self.post('products', data) # Order endpoints def get_orders(self, page=1, per_page=100, **kwargs): params = {'page': page, 'per_page': per_page, **kwargs} return self.get('orders', params=params) def get_order(self, order_id): return self.get(f'orders/{order_id}') def update_order(self, order_id, data): return self.put(f'orders/{order_id}', data) # Customer endpoints def get_customers(self, page=1, per_page=100, **kwargs): params = {'page': page, 'per_page': per_page, **kwargs} return self.get('customers', params=params) def get_customer(self, customer_id): return self.get(f'customers/{customer_id}') def create_customer(self, data): return self.post('customers', data) def update_customer(self, customer_id, data): return self.put(f'customers/{customer_id}', data) # Webhook endpoints def create_webhook(self, data): return self.post('webhooks', data) def get_webhooks(self): return self.get('webhooks', params={'per_page': 100}) def delete_webhook(self, webhook_id): return self.delete(f'webhooks/{webhook_id}') # Tax endpoints def get_tax_classes(self): return self.get('taxes/classes') # Utility def test_connection(self): try: result = self.get('system_status') wc_version = result.get('environment', {}).get('version', 'unknown') return True, wc_version except Exception as exc: return False, str(exc) @staticmethod def verify_webhook_signature(payload, signature, secret): """Verify a WooCommerce webhook HMAC-SHA256 signature. Args: payload (bytes): Raw request body bytes. signature (str): Value of the X-WC-Webhook-Signature header. secret (str): The webhook secret configured in WooCommerce. Returns: bool: True if the signature matches, False otherwise. """ if isinstance(payload, str): payload = payload.encode('utf-8') if isinstance(secret, str): secret = secret.encode('utf-8') computed = base64.b64encode( hmac.new(secret, payload, hashlib.sha256).digest() ).decode('utf-8') return hmac.compare_digest(computed, signature)