feat: add WooCommerce REST API v3 client wrapper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,2 @@
|
||||
# Library helpers will be imported here
|
||||
from .woo_api_client import WooApiClient
|
||||
|
||||
160
fusion-woo-odoo/fusion_woocommerce/lib/woo_api_client.py
Normal file
160
fusion-woo-odoo/fusion_woocommerce/lib/woo_api_client.py
Normal file
@@ -0,0 +1,160 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user