feat: 初始化 B2B AI Shopping Assistant 项目
- 配置 Docker Compose 多服务编排 - 实现 Chatwoot + Agent 集成 - 配置 Strapi MCP 知识库 - 支持 7 种语言的 FAQ 系统 - 实现 LangGraph AI 工作流 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
14
agent/integrations/__init__.py
Normal file
14
agent/integrations/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Agent integrations package"""
|
||||
from .chatwoot import ChatwootClient, get_chatwoot_client, MessageType, ConversationStatus
|
||||
from .hyperf_client import HyperfClient, get_hyperf_client, APIResponse, APIError
|
||||
|
||||
__all__ = [
|
||||
"ChatwootClient",
|
||||
"get_chatwoot_client",
|
||||
"MessageType",
|
||||
"ConversationStatus",
|
||||
"HyperfClient",
|
||||
"get_hyperf_client",
|
||||
"APIResponse",
|
||||
"APIError",
|
||||
]
|
||||
354
agent/integrations/chatwoot.py
Normal file
354
agent/integrations/chatwoot.py
Normal file
@@ -0,0 +1,354 @@
|
||||
"""
|
||||
Chatwoot API Client for B2B Shopping AI Assistant
|
||||
"""
|
||||
from typing import Any, Optional
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
import httpx
|
||||
|
||||
from config import settings
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class MessageType(str, Enum):
|
||||
"""Chatwoot message types"""
|
||||
INCOMING = "incoming"
|
||||
OUTGOING = "outgoing"
|
||||
|
||||
|
||||
class ConversationStatus(str, Enum):
|
||||
"""Chatwoot conversation status"""
|
||||
OPEN = "open"
|
||||
RESOLVED = "resolved"
|
||||
PENDING = "pending"
|
||||
SNOOZED = "snoozed"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatwootMessage:
|
||||
"""Chatwoot message structure"""
|
||||
id: int
|
||||
content: str
|
||||
message_type: str
|
||||
conversation_id: int
|
||||
sender_type: Optional[str] = None
|
||||
sender_id: Optional[int] = None
|
||||
private: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatwootContact:
|
||||
"""Chatwoot contact structure"""
|
||||
id: int
|
||||
name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
phone_number: Optional[str] = None
|
||||
custom_attributes: Optional[dict[str, Any]] = None
|
||||
|
||||
|
||||
class ChatwootClient:
|
||||
"""Chatwoot API Client"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_url: Optional[str] = None,
|
||||
api_token: Optional[str] = None,
|
||||
account_id: int = 1
|
||||
):
|
||||
"""Initialize Chatwoot client
|
||||
|
||||
Args:
|
||||
api_url: Chatwoot API URL, defaults to settings
|
||||
api_token: API access token, defaults to settings
|
||||
account_id: Chatwoot account ID
|
||||
"""
|
||||
self.api_url = (api_url or settings.chatwoot_api_url).rstrip("/")
|
||||
self.api_token = api_token or settings.chatwoot_api_token
|
||||
self.account_id = account_id
|
||||
self._client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
logger.info("Chatwoot client initialized", api_url=self.api_url)
|
||||
|
||||
async def _get_client(self) -> httpx.AsyncClient:
|
||||
"""Get or create HTTP client"""
|
||||
if self._client is None:
|
||||
self._client = httpx.AsyncClient(
|
||||
base_url=f"{self.api_url}/api/v1/accounts/{self.account_id}",
|
||||
headers={
|
||||
"api_access_token": self.api_token,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
timeout=30.0
|
||||
)
|
||||
return self._client
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Close HTTP client"""
|
||||
if self._client:
|
||||
await self._client.aclose()
|
||||
self._client = None
|
||||
|
||||
# ============ Messages ============
|
||||
|
||||
async def send_message(
|
||||
self,
|
||||
conversation_id: int,
|
||||
content: str,
|
||||
message_type: MessageType = MessageType.OUTGOING,
|
||||
private: bool = False
|
||||
) -> dict[str, Any]:
|
||||
"""Send a message to a conversation
|
||||
|
||||
Args:
|
||||
conversation_id: Conversation ID
|
||||
content: Message content
|
||||
message_type: Message type (incoming/outgoing)
|
||||
private: Whether message is private (internal note)
|
||||
|
||||
Returns:
|
||||
Created message data
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
payload = {
|
||||
"content": content,
|
||||
"message_type": message_type.value,
|
||||
"private": private
|
||||
}
|
||||
|
||||
response = await client.post(
|
||||
f"/conversations/{conversation_id}/messages",
|
||||
json=payload
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
logger.debug(
|
||||
"Message sent",
|
||||
conversation_id=conversation_id,
|
||||
message_id=data.get("id")
|
||||
)
|
||||
return data
|
||||
|
||||
async def send_rich_message(
|
||||
self,
|
||||
conversation_id: int,
|
||||
content: str,
|
||||
content_type: str,
|
||||
content_attributes: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Send a rich message (cards, buttons, etc.)
|
||||
|
||||
Args:
|
||||
conversation_id: Conversation ID
|
||||
content: Fallback text content
|
||||
content_type: Rich content type (cards, input_select, etc.)
|
||||
content_attributes: Rich content attributes
|
||||
|
||||
Returns:
|
||||
Created message data
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
payload = {
|
||||
"content": content,
|
||||
"message_type": MessageType.OUTGOING.value,
|
||||
"content_type": content_type,
|
||||
"content_attributes": content_attributes
|
||||
}
|
||||
|
||||
response = await client.post(
|
||||
f"/conversations/{conversation_id}/messages",
|
||||
json=payload
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json()
|
||||
|
||||
# ============ Conversations ============
|
||||
|
||||
async def get_conversation(self, conversation_id: int) -> dict[str, Any]:
|
||||
"""Get conversation details
|
||||
|
||||
Args:
|
||||
conversation_id: Conversation ID
|
||||
|
||||
Returns:
|
||||
Conversation data
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
response = await client.get(f"/conversations/{conversation_id}")
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json()
|
||||
|
||||
async def update_conversation_status(
|
||||
self,
|
||||
conversation_id: int,
|
||||
status: ConversationStatus
|
||||
) -> dict[str, Any]:
|
||||
"""Update conversation status
|
||||
|
||||
Args:
|
||||
conversation_id: Conversation ID
|
||||
status: New status
|
||||
|
||||
Returns:
|
||||
Updated conversation data
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
response = await client.post(
|
||||
f"/conversations/{conversation_id}/toggle_status",
|
||||
json={"status": status.value}
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
logger.info(
|
||||
"Conversation status updated",
|
||||
conversation_id=conversation_id,
|
||||
status=status.value
|
||||
)
|
||||
return response.json()
|
||||
|
||||
async def add_labels(
|
||||
self,
|
||||
conversation_id: int,
|
||||
labels: list[str]
|
||||
) -> dict[str, Any]:
|
||||
"""Add labels to a conversation
|
||||
|
||||
Args:
|
||||
conversation_id: Conversation ID
|
||||
labels: List of label names
|
||||
|
||||
Returns:
|
||||
Updated labels
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
response = await client.post(
|
||||
f"/conversations/{conversation_id}/labels",
|
||||
json={"labels": labels}
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json()
|
||||
|
||||
async def assign_agent(
|
||||
self,
|
||||
conversation_id: int,
|
||||
agent_id: int
|
||||
) -> dict[str, Any]:
|
||||
"""Assign an agent to a conversation
|
||||
|
||||
Args:
|
||||
conversation_id: Conversation ID
|
||||
agent_id: Agent user ID
|
||||
|
||||
Returns:
|
||||
Assignment result
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
response = await client.post(
|
||||
f"/conversations/{conversation_id}/assignments",
|
||||
json={"assignee_id": agent_id}
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
logger.info(
|
||||
"Agent assigned",
|
||||
conversation_id=conversation_id,
|
||||
agent_id=agent_id
|
||||
)
|
||||
return response.json()
|
||||
|
||||
# ============ Contacts ============
|
||||
|
||||
async def get_contact(self, contact_id: int) -> dict[str, Any]:
|
||||
"""Get contact details
|
||||
|
||||
Args:
|
||||
contact_id: Contact ID
|
||||
|
||||
Returns:
|
||||
Contact data
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
response = await client.get(f"/contacts/{contact_id}")
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json()
|
||||
|
||||
async def update_contact(
|
||||
self,
|
||||
contact_id: int,
|
||||
attributes: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Update contact attributes
|
||||
|
||||
Args:
|
||||
contact_id: Contact ID
|
||||
attributes: Attributes to update
|
||||
|
||||
Returns:
|
||||
Updated contact data
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
response = await client.put(
|
||||
f"/contacts/{contact_id}",
|
||||
json=attributes
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json()
|
||||
|
||||
# ============ Messages History ============
|
||||
|
||||
async def get_messages(
|
||||
self,
|
||||
conversation_id: int,
|
||||
before: Optional[int] = None
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Get conversation messages
|
||||
|
||||
Args:
|
||||
conversation_id: Conversation ID
|
||||
before: Get messages before this message ID
|
||||
|
||||
Returns:
|
||||
List of messages
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
params = {}
|
||||
if before:
|
||||
params["before"] = before
|
||||
|
||||
response = await client.get(
|
||||
f"/conversations/{conversation_id}/messages",
|
||||
params=params
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
return data.get("payload", [])
|
||||
|
||||
|
||||
# Global Chatwoot client instance
|
||||
chatwoot_client: Optional[ChatwootClient] = None
|
||||
|
||||
|
||||
def get_chatwoot_client() -> ChatwootClient:
|
||||
"""Get or create global Chatwoot client instance"""
|
||||
global chatwoot_client
|
||||
if chatwoot_client is None:
|
||||
chatwoot_client = ChatwootClient()
|
||||
return chatwoot_client
|
||||
538
agent/integrations/hyperf_client.py
Normal file
538
agent/integrations/hyperf_client.py
Normal file
@@ -0,0 +1,538 @@
|
||||
"""
|
||||
Hyperf PHP API Client for B2B Shopping AI Assistant
|
||||
"""
|
||||
from typing import Any, Optional
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
import httpx
|
||||
|
||||
from config import settings
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class APIError(Exception):
|
||||
"""API error with code and message"""
|
||||
def __init__(self, code: int, message: str, data: Optional[Any] = None):
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.data = data
|
||||
super().__init__(f"[{code}] {message}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class APIResponse:
|
||||
"""Standardized API response"""
|
||||
code: int
|
||||
message: str
|
||||
data: Any
|
||||
meta: Optional[dict[str, Any]] = None
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
return self.code == 0
|
||||
|
||||
|
||||
class HyperfClient:
|
||||
"""Hyperf PHP API Client"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_url: Optional[str] = None,
|
||||
api_token: Optional[str] = None
|
||||
):
|
||||
"""Initialize Hyperf client
|
||||
|
||||
Args:
|
||||
api_url: Hyperf API base URL, defaults to settings
|
||||
api_token: API access token, defaults to settings
|
||||
"""
|
||||
self.api_url = (api_url or settings.hyperf_api_url).rstrip("/")
|
||||
self.api_token = api_token or settings.hyperf_api_token
|
||||
self._client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
logger.info("Hyperf client initialized", api_url=self.api_url)
|
||||
|
||||
async def _get_client(self) -> httpx.AsyncClient:
|
||||
"""Get or create HTTP client"""
|
||||
if self._client is None:
|
||||
self._client = httpx.AsyncClient(
|
||||
base_url=f"{self.api_url}/api/v1",
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.api_token}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
timeout=30.0
|
||||
)
|
||||
return self._client
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Close HTTP client"""
|
||||
if self._client:
|
||||
await self._client.aclose()
|
||||
self._client = None
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
json: Optional[dict[str, Any]] = None,
|
||||
headers: Optional[dict[str, str]] = None
|
||||
) -> APIResponse:
|
||||
"""Make API request
|
||||
|
||||
Args:
|
||||
method: HTTP method
|
||||
endpoint: API endpoint
|
||||
params: Query parameters
|
||||
json: JSON body
|
||||
headers: Additional headers
|
||||
|
||||
Returns:
|
||||
Parsed API response
|
||||
|
||||
Raises:
|
||||
APIError: If API returns error
|
||||
"""
|
||||
client = await self._get_client()
|
||||
|
||||
# Merge headers
|
||||
request_headers = {}
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
|
||||
logger.debug(
|
||||
"API request",
|
||||
method=method,
|
||||
endpoint=endpoint
|
||||
)
|
||||
|
||||
try:
|
||||
response = await client.request(
|
||||
method=method,
|
||||
url=endpoint,
|
||||
params=params,
|
||||
json=json,
|
||||
headers=request_headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
|
||||
result = APIResponse(
|
||||
code=data.get("code", 0),
|
||||
message=data.get("message", "success"),
|
||||
data=data.get("data"),
|
||||
meta=data.get("meta")
|
||||
)
|
||||
|
||||
if not result.success:
|
||||
raise APIError(result.code, result.message, result.data)
|
||||
|
||||
logger.debug(
|
||||
"API response",
|
||||
endpoint=endpoint,
|
||||
code=result.code
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.error(
|
||||
"HTTP error",
|
||||
endpoint=endpoint,
|
||||
status_code=e.response.status_code
|
||||
)
|
||||
raise APIError(
|
||||
e.response.status_code,
|
||||
f"HTTP error: {e.response.status_code}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("API request failed", endpoint=endpoint, error=str(e))
|
||||
raise
|
||||
|
||||
async def get(
|
||||
self,
|
||||
endpoint: str,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
**kwargs: Any
|
||||
) -> APIResponse:
|
||||
"""GET request"""
|
||||
return await self._request("GET", endpoint, params=params, **kwargs)
|
||||
|
||||
async def post(
|
||||
self,
|
||||
endpoint: str,
|
||||
json: Optional[dict[str, Any]] = None,
|
||||
**kwargs: Any
|
||||
) -> APIResponse:
|
||||
"""POST request"""
|
||||
return await self._request("POST", endpoint, json=json, **kwargs)
|
||||
|
||||
async def put(
|
||||
self,
|
||||
endpoint: str,
|
||||
json: Optional[dict[str, Any]] = None,
|
||||
**kwargs: Any
|
||||
) -> APIResponse:
|
||||
"""PUT request"""
|
||||
return await self._request("PUT", endpoint, json=json, **kwargs)
|
||||
|
||||
async def delete(
|
||||
self,
|
||||
endpoint: str,
|
||||
**kwargs: Any
|
||||
) -> APIResponse:
|
||||
"""DELETE request"""
|
||||
return await self._request("DELETE", endpoint, **kwargs)
|
||||
|
||||
# ============ Order APIs ============
|
||||
|
||||
async def query_orders(
|
||||
self,
|
||||
user_id: str,
|
||||
account_id: str,
|
||||
order_id: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
date_start: Optional[str] = None,
|
||||
date_end: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20
|
||||
) -> APIResponse:
|
||||
"""Query orders
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
account_id: Account ID
|
||||
order_id: Optional specific order ID
|
||||
status: Optional order status filter
|
||||
date_start: Optional start date (YYYY-MM-DD)
|
||||
date_end: Optional end date (YYYY-MM-DD)
|
||||
page: Page number
|
||||
page_size: Items per page
|
||||
|
||||
Returns:
|
||||
Orders list response
|
||||
"""
|
||||
payload = {
|
||||
"user_id": user_id,
|
||||
"account_id": account_id,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
if order_id:
|
||||
payload["order_id"] = order_id
|
||||
if status:
|
||||
payload["status"] = status
|
||||
if date_start:
|
||||
payload["date_range"] = {"start": date_start}
|
||||
if date_end:
|
||||
payload.setdefault("date_range", {})["end"] = date_end
|
||||
|
||||
return await self.post("/orders/query", json=payload)
|
||||
|
||||
async def get_logistics(
|
||||
self,
|
||||
order_id: str,
|
||||
tracking_number: Optional[str] = None
|
||||
) -> APIResponse:
|
||||
"""Get order logistics information
|
||||
|
||||
Args:
|
||||
order_id: Order ID
|
||||
tracking_number: Optional tracking number
|
||||
|
||||
Returns:
|
||||
Logistics tracking response
|
||||
"""
|
||||
params = {}
|
||||
if tracking_number:
|
||||
params["tracking_number"] = tracking_number
|
||||
|
||||
return await self.get(f"/orders/{order_id}/logistics", params=params)
|
||||
|
||||
async def modify_order(
|
||||
self,
|
||||
order_id: str,
|
||||
user_id: str,
|
||||
modifications: dict[str, Any]
|
||||
) -> APIResponse:
|
||||
"""Modify order
|
||||
|
||||
Args:
|
||||
order_id: Order ID
|
||||
user_id: User ID for permission check
|
||||
modifications: Changes to apply
|
||||
|
||||
Returns:
|
||||
Modified order response
|
||||
"""
|
||||
return await self.put(
|
||||
f"/orders/{order_id}/modify",
|
||||
json={
|
||||
"user_id": user_id,
|
||||
"modifications": modifications
|
||||
}
|
||||
)
|
||||
|
||||
async def cancel_order(
|
||||
self,
|
||||
order_id: str,
|
||||
user_id: str,
|
||||
reason: str
|
||||
) -> APIResponse:
|
||||
"""Cancel order
|
||||
|
||||
Args:
|
||||
order_id: Order ID
|
||||
user_id: User ID for permission check
|
||||
reason: Cancellation reason
|
||||
|
||||
Returns:
|
||||
Cancellation result with refund info
|
||||
"""
|
||||
return await self.post(
|
||||
f"/orders/{order_id}/cancel",
|
||||
json={
|
||||
"user_id": user_id,
|
||||
"reason": reason
|
||||
}
|
||||
)
|
||||
|
||||
# ============ Product APIs ============
|
||||
|
||||
async def search_products(
|
||||
self,
|
||||
query: str,
|
||||
filters: Optional[dict[str, Any]] = None,
|
||||
sort: str = "relevance",
|
||||
page: int = 1,
|
||||
page_size: int = 20
|
||||
) -> APIResponse:
|
||||
"""Search products
|
||||
|
||||
Args:
|
||||
query: Search query
|
||||
filters: Optional filters (category, price_range, brand, etc.)
|
||||
sort: Sort order
|
||||
page: Page number
|
||||
page_size: Items per page
|
||||
|
||||
Returns:
|
||||
Products list response
|
||||
"""
|
||||
payload = {
|
||||
"query": query,
|
||||
"sort": sort,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
if filters:
|
||||
payload["filters"] = filters
|
||||
|
||||
return await self.post("/products/search", json=payload)
|
||||
|
||||
async def get_product(self, product_id: str) -> APIResponse:
|
||||
"""Get product details
|
||||
|
||||
Args:
|
||||
product_id: Product ID
|
||||
|
||||
Returns:
|
||||
Product details response
|
||||
"""
|
||||
return await self.get(f"/products/{product_id}")
|
||||
|
||||
async def get_recommendations(
|
||||
self,
|
||||
user_id: str,
|
||||
account_id: str,
|
||||
context: Optional[dict[str, Any]] = None,
|
||||
limit: int = 10
|
||||
) -> APIResponse:
|
||||
"""Get product recommendations
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
account_id: Account ID
|
||||
context: Optional context (recent views, current query)
|
||||
limit: Number of recommendations
|
||||
|
||||
Returns:
|
||||
Recommendations response
|
||||
"""
|
||||
payload = {
|
||||
"user_id": user_id,
|
||||
"account_id": account_id,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
if context:
|
||||
payload["context"] = context
|
||||
|
||||
return await self.post("/products/recommend", json=payload)
|
||||
|
||||
async def get_quote(
|
||||
self,
|
||||
product_id: str,
|
||||
quantity: int,
|
||||
account_id: str,
|
||||
delivery_address: Optional[dict[str, str]] = None
|
||||
) -> APIResponse:
|
||||
"""Get B2B price quote
|
||||
|
||||
Args:
|
||||
product_id: Product ID
|
||||
quantity: Quantity
|
||||
account_id: Account ID for pricing tier
|
||||
delivery_address: Optional delivery address
|
||||
|
||||
Returns:
|
||||
Quote response with pricing details
|
||||
"""
|
||||
payload = {
|
||||
"product_id": product_id,
|
||||
"quantity": quantity,
|
||||
"account_id": account_id
|
||||
}
|
||||
|
||||
if delivery_address:
|
||||
payload["delivery_address"] = delivery_address
|
||||
|
||||
return await self.post("/products/quote", json=payload)
|
||||
|
||||
# ============ Aftersale APIs ============
|
||||
|
||||
async def apply_return(
|
||||
self,
|
||||
order_id: str,
|
||||
user_id: str,
|
||||
items: list[dict[str, Any]],
|
||||
description: str,
|
||||
images: Optional[list[str]] = None
|
||||
) -> APIResponse:
|
||||
"""Apply for return
|
||||
|
||||
Args:
|
||||
order_id: Order ID
|
||||
user_id: User ID
|
||||
items: Items to return with quantity and reason
|
||||
description: Description of issue
|
||||
images: Optional image URLs
|
||||
|
||||
Returns:
|
||||
Return application response
|
||||
"""
|
||||
payload = {
|
||||
"order_id": order_id,
|
||||
"user_id": user_id,
|
||||
"items": items,
|
||||
"description": description
|
||||
}
|
||||
|
||||
if images:
|
||||
payload["images"] = images
|
||||
|
||||
return await self.post("/aftersales/return", json=payload)
|
||||
|
||||
async def apply_exchange(
|
||||
self,
|
||||
order_id: str,
|
||||
user_id: str,
|
||||
items: list[dict[str, Any]],
|
||||
description: str
|
||||
) -> APIResponse:
|
||||
"""Apply for exchange
|
||||
|
||||
Args:
|
||||
order_id: Order ID
|
||||
user_id: User ID
|
||||
items: Items to exchange with reason
|
||||
description: Description of issue
|
||||
|
||||
Returns:
|
||||
Exchange application response
|
||||
"""
|
||||
return await self.post(
|
||||
"/aftersales/exchange",
|
||||
json={
|
||||
"order_id": order_id,
|
||||
"user_id": user_id,
|
||||
"items": items,
|
||||
"description": description
|
||||
}
|
||||
)
|
||||
|
||||
async def create_complaint(
|
||||
self,
|
||||
user_id: str,
|
||||
complaint_type: str,
|
||||
title: str,
|
||||
description: str,
|
||||
related_order_id: Optional[str] = None,
|
||||
attachments: Optional[list[str]] = None
|
||||
) -> APIResponse:
|
||||
"""Create complaint
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
complaint_type: Type of complaint
|
||||
title: Complaint title
|
||||
description: Detailed description
|
||||
related_order_id: Optional related order
|
||||
attachments: Optional attachment URLs
|
||||
|
||||
Returns:
|
||||
Complaint creation response
|
||||
"""
|
||||
payload = {
|
||||
"user_id": user_id,
|
||||
"type": complaint_type,
|
||||
"title": title,
|
||||
"description": description
|
||||
}
|
||||
|
||||
if related_order_id:
|
||||
payload["related_order_id"] = related_order_id
|
||||
if attachments:
|
||||
payload["attachments"] = attachments
|
||||
|
||||
return await self.post("/aftersales/complaint", json=payload)
|
||||
|
||||
async def query_aftersales(
|
||||
self,
|
||||
user_id: str,
|
||||
aftersale_id: Optional[str] = None
|
||||
) -> APIResponse:
|
||||
"""Query aftersale records
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
aftersale_id: Optional specific aftersale ID
|
||||
|
||||
Returns:
|
||||
Aftersale records response
|
||||
"""
|
||||
params = {"user_id": user_id}
|
||||
if aftersale_id:
|
||||
params["aftersale_id"] = aftersale_id
|
||||
|
||||
return await self.get("/aftersales/query", params=params)
|
||||
|
||||
|
||||
# Global Hyperf client instance
|
||||
hyperf_client: Optional[HyperfClient] = None
|
||||
|
||||
|
||||
def get_hyperf_client() -> HyperfClient:
|
||||
"""Get or create global Hyperf client instance"""
|
||||
global hyperf_client
|
||||
if hyperf_client is None:
|
||||
hyperf_client = HyperfClient()
|
||||
return hyperf_client
|
||||
Reference in New Issue
Block a user