Add complete Letzshop marketplace integration with: - GraphQL client for order import and fulfillment operations - Encrypted credential storage per vendor (Fernet encryption) - Admin and vendor API endpoints for credentials management - Order import, confirmation, rejection, and tracking - Fulfillment queue and sync logging - Comprehensive documentation and test coverage New files: - app/services/letzshop/ - GraphQL client and services - app/utils/encryption.py - Fernet encryption utility - models/database/letzshop.py - Database models - models/schema/letzshop.py - Pydantic schemas - app/api/v1/admin/letzshop.py - Admin API endpoints - app/api/v1/vendor/letzshop.py - Vendor API endpoints - docs/guides/letzshop-order-integration.md - Documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
17 KiB
Letzshop Order Integration Guide
Complete guide for bidirectional order management with Letzshop marketplace via GraphQL API.
Table of Contents
- Overview
- Architecture
- Setup and Configuration
- Order Import
- Fulfillment Operations
- API Reference
- Database Models
- Troubleshooting
Overview
The Letzshop Order Integration provides bidirectional synchronization with Letzshop marketplace:
- Order Import: Fetch unconfirmed orders from Letzshop via GraphQL
- Order Confirmation: Confirm or reject inventory units
- Tracking Updates: Set shipment tracking information
- Audit Trail: Complete logging of all sync operations
Key Features
- Encrypted Credentials: API keys stored with Fernet encryption
- Per-Vendor Configuration: Each vendor manages their own Letzshop connection
- Admin Oversight: Platform admins can manage any vendor's integration
- Queue-Based Fulfillment: Retry logic for failed operations
- Multi-Channel Support: Orders tracked with channel attribution
Architecture
System Components
┌─────────────────────────────────────────┐
│ Frontend Interfaces │
├─────────────────────────────────────────┤
│ Vendor Portal Admin Portal │
│ /vendor/letzshop /admin/letzshop │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ API Layer │
├─────────────────────────────────────────┤
│ /api/v1/vendor/letzshop/* │
│ /api/v1/admin/letzshop/* │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ Service Layer │
├─────────────────────────────────────────┤
│ LetzshopClient CredentialsService│
│ (GraphQL) (Encryption) │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ Data Layer │
├─────────────────────────────────────────┤
│ VendorLetzshopCredentials │
│ LetzshopOrder │
│ LetzshopFulfillmentQueue │
│ LetzshopSyncLog │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ Letzshop GraphQL API │
│ https://letzshop.lu/graphql │
└─────────────────────────────────────────┘
Data Flow
- Credentials Setup: Vendor/Admin stores encrypted API key
- Order Import: System fetches unconfirmed shipments from Letzshop
- Order Processing: Orders stored locally with Letzshop IDs
- Fulfillment: Vendor confirms/rejects orders, sets tracking
- Sync Back: Operations sent to Letzshop via GraphQL mutations
Setup and Configuration
Prerequisites
- Letzshop API key (obtained from Letzshop merchant portal)
- Active vendor account on the platform
Step 1: Configure API Credentials
Via Vendor Portal
- Navigate to Settings > Letzshop Integration
- Enter your Letzshop API key
- Click Test Connection to verify
- Enable Auto-Sync if desired (optional)
- Click Save
Via Admin Portal
- Navigate to Marketplace > Letzshop
- Select the vendor from the list
- Click Configure Credentials
- Enter the API key
- Click Save & Test
Step 2: Test Connection
# Test connection via API
curl -X POST /api/v1/vendor/letzshop/test \
-H "Authorization: Bearer $TOKEN"
Response:
{
"success": true,
"message": "Connection successful",
"response_time_ms": 245.5
}
Configuration Options
| Setting | Default | Description |
|---|---|---|
api_endpoint |
https://letzshop.lu/graphql |
GraphQL endpoint URL |
auto_sync_enabled |
false |
Enable automatic order sync |
sync_interval_minutes |
15 |
Auto-sync interval (5-1440 minutes) |
Order Import
Manual Import
Import orders on-demand via the vendor portal or API:
# Trigger order import
curl -X POST /api/v1/vendor/letzshop/orders/import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"operation": "order_import"}'
Response:
{
"success": true,
"message": "Import completed: 5 imported, 2 updated",
"orders_imported": 5,
"orders_updated": 2,
"errors": []
}
What Gets Imported
The import fetches unconfirmed shipments from Letzshop containing:
- Order ID and number
- Customer email and name
- Order total and currency
- Inventory units (products to fulfill)
- Shipping/billing addresses
- Current order state
Order States
| Letzshop State | Description |
|---|---|
unconfirmed |
Awaiting vendor confirmation |
confirmed |
Vendor confirmed, ready to ship |
shipped |
Tracking number set |
delivered |
Delivery confirmed |
returned |
Items returned |
Sync Status
Local orders track their sync status:
| Status | Description |
|---|---|
pending |
Imported, awaiting action |
confirmed |
Confirmed with Letzshop |
rejected |
Rejected with Letzshop |
shipped |
Tracking set with Letzshop |
Fulfillment Operations
Confirm Order
Confirm that you can fulfill the order:
# Confirm all inventory units in an order
curl -X POST /api/v1/vendor/letzshop/orders/{order_id}/confirm \
-H "Authorization: Bearer $TOKEN"
# Or confirm specific units
curl -X POST /api/v1/vendor/letzshop/orders/{order_id}/confirm \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"inventory_unit_ids": ["unit_abc123", "unit_def456"]}'
Reject Order
Reject order if you cannot fulfill:
curl -X POST /api/v1/vendor/letzshop/orders/{order_id}/reject \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason": "Out of stock"}'
Set Tracking
Add tracking information for shipment:
curl -X POST /api/v1/vendor/letzshop/orders/{order_id}/tracking \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tracking_number": "1Z999AA10123456784",
"tracking_carrier": "ups"
}'
Supported carriers: dhl, ups, fedex, post_lu, etc.
API Reference
Vendor Endpoints
Base path: /api/v1/vendor/letzshop
| Method | Endpoint | Description |
|---|---|---|
| GET | /status |
Get integration status |
| GET | /credentials |
Get credentials (API key masked) |
| POST | /credentials |
Create/update credentials |
| PATCH | /credentials |
Partial update credentials |
| DELETE | /credentials |
Remove credentials |
| POST | /test |
Test stored credentials |
| POST | /test-key |
Test API key without saving |
| GET | /orders |
List Letzshop orders |
| GET | /orders/{id} |
Get order details |
| POST | /orders/import |
Import orders from Letzshop |
| POST | /orders/{id}/confirm |
Confirm order |
| POST | /orders/{id}/reject |
Reject order |
| POST | /orders/{id}/tracking |
Set tracking info |
| GET | /logs |
List sync logs |
| GET | /queue |
List fulfillment queue |
Admin Endpoints
Base path: /api/v1/admin/letzshop
| Method | Endpoint | Description |
|---|---|---|
| GET | /vendors |
List vendors with Letzshop status |
| GET | /vendors/{id}/credentials |
Get vendor credentials |
| POST | /vendors/{id}/credentials |
Set vendor credentials |
| PATCH | /vendors/{id}/credentials |
Update vendor credentials |
| DELETE | /vendors/{id}/credentials |
Delete vendor credentials |
| POST | /vendors/{id}/test |
Test vendor connection |
| POST | /test |
Test any API key |
| GET | /vendors/{id}/orders |
List vendor's Letzshop orders |
| POST | /vendors/{id}/sync |
Trigger sync for vendor |
Response Schemas
Credentials Response
{
"id": 1,
"vendor_id": 5,
"api_key_masked": "letz****",
"api_endpoint": "https://letzshop.lu/graphql",
"auto_sync_enabled": false,
"sync_interval_minutes": 15,
"last_sync_at": "2025-01-15T10:30:00Z",
"last_sync_status": "success",
"last_sync_error": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
Order Response
{
"id": 123,
"vendor_id": 5,
"letzshop_order_id": "gid://letzshop/Order/12345",
"letzshop_shipment_id": "gid://letzshop/Shipment/67890",
"letzshop_order_number": "LS-2025-001234",
"letzshop_state": "unconfirmed",
"customer_email": "customer@example.com",
"customer_name": "John Doe",
"total_amount": "99.99",
"currency": "EUR",
"sync_status": "pending",
"inventory_units": [
{"id": "gid://letzshop/InventoryUnit/111", "state": "unconfirmed"}
],
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}
Database Models
VendorLetzshopCredentials
Stores encrypted API credentials per vendor.
class VendorLetzshopCredentials(Base):
__tablename__ = "vendor_letzshop_credentials"
id: int # Primary key
vendor_id: int # FK to vendors (unique)
api_key_encrypted: str # Fernet encrypted API key
api_endpoint: str # GraphQL endpoint URL
auto_sync_enabled: bool # Enable auto-sync
sync_interval_minutes: int # Sync interval
last_sync_at: datetime # Last sync timestamp
last_sync_status: str # success, failed, partial
last_sync_error: str # Error message if failed
LetzshopOrder
Tracks imported orders from Letzshop.
class LetzshopOrder(Base):
__tablename__ = "letzshop_orders"
id: int # Primary key
vendor_id: int # FK to vendors
letzshop_order_id: str # Letzshop order GID
letzshop_shipment_id: str # Letzshop shipment GID
letzshop_order_number: str # Human-readable order number
local_order_id: int # FK to orders (if imported locally)
letzshop_state: str # Current Letzshop state
customer_email: str # Customer email
customer_name: str # Customer name
total_amount: str # Order total
currency: str # Currency code
raw_order_data: JSON # Full order data from Letzshop
inventory_units: JSON # List of inventory units
sync_status: str # pending, confirmed, rejected, shipped
tracking_number: str # Tracking number (if set)
tracking_carrier: str # Carrier code
LetzshopFulfillmentQueue
Queue for outbound operations with retry logic.
class LetzshopFulfillmentQueue(Base):
__tablename__ = "letzshop_fulfillment_queue"
id: int # Primary key
vendor_id: int # FK to vendors
letzshop_order_id: int # FK to letzshop_orders
operation: str # confirm, reject, set_tracking
payload: JSON # Operation data
status: str # pending, processing, completed, failed
attempts: int # Retry count
max_attempts: int # Max retries (default 3)
error_message: str # Last error if failed
response_data: JSON # Response from Letzshop
LetzshopSyncLog
Audit trail for all sync operations.
class LetzshopSyncLog(Base):
__tablename__ = "letzshop_sync_logs"
id: int # Primary key
vendor_id: int # FK to vendors
operation_type: str # order_import, confirm, etc.
direction: str # inbound, outbound
status: str # success, failed, partial
records_processed: int # Total records
records_succeeded: int # Successful records
records_failed: int # Failed records
error_details: JSON # Detailed error info
started_at: datetime # Operation start time
completed_at: datetime # Operation end time
duration_seconds: int # Total duration
triggered_by: str # user_id, scheduler, webhook
Security
API Key Encryption
API keys are encrypted using Fernet symmetric encryption:
from app.utils.encryption import encrypt_value, decrypt_value
# Encrypt before storing
encrypted_key = encrypt_value(api_key)
# Decrypt when needed
api_key = decrypt_value(encrypted_key)
The encryption key is derived from the application's jwt_secret_key using PBKDF2.
Access Control
- Vendors: Can only manage their own Letzshop integration
- Admins: Can manage any vendor's integration
- API Keys: Never returned in plain text (always masked)
Troubleshooting
Connection Failed
Symptoms: "Connection failed" error when testing
Possible Causes:
- Invalid API key
- API key expired
- Network issues
- Letzshop service unavailable
Solutions:
- Verify API key in Letzshop merchant portal
- Regenerate API key if expired
- Check network connectivity
- Check Letzshop status page
Orders Not Importing
Symptoms: Import runs but no orders appear
Possible Causes:
- No unconfirmed orders in Letzshop
- API key doesn't have required permissions
- Orders already imported
Solutions:
- Check Letzshop dashboard for unconfirmed orders
- Verify API key has order read permissions
- Check existing orders with
sync_status: pending
Fulfillment Failed
Symptoms: Confirm/reject/tracking operations fail
Possible Causes:
- Order already processed
- Invalid inventory unit IDs
- API permission issues
Solutions:
- Check order state in Letzshop
- Verify inventory unit IDs are correct
- Check fulfillment queue for retry status
- Review error message in response
Sync Logs
Check sync logs for detailed operation history:
curl -X GET /api/v1/vendor/letzshop/logs \
-H "Authorization: Bearer $TOKEN"
Best Practices
For Vendors
- Test connection after setting up credentials
- Import orders regularly (or enable auto-sync)
- Confirm orders promptly to avoid delays
- Set tracking as soon as shipment is dispatched
- Monitor sync logs for any failures
For Admins
- Review vendor status regularly via admin dashboard
- Assist vendors with connection issues
- Monitor sync logs for platform-wide issues
- Set up alerts for failed syncs (optional)
Related Documentation
Version History
- v1.0 (2025-12-13): Initial Letzshop order integration
- GraphQL client for order import
- Encrypted credential storage
- Fulfillment operations (confirm, reject, tracking)
- Admin and vendor API endpoints
- Sync logging and queue management