Move 39 documentation files from top-level docs/ into each module's docs/ folder, accessible via symlinks from docs/modules/. Create data-model.md files for 10 modules with full schema documentation. Replace originals with redirect stubs. Remove empty guide stubs. Modules migrated: tenancy, billing, loyalty, marketplace, orders, messaging, cms, catalog, inventory, hosting, prospecting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
26 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
- Product Exceptions
- Fulfillment Operations
- Shipping and Tracking
- 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-Store Configuration: Each store manages their own Letzshop connection
- Admin Oversight: Platform admins can manage any store's integration
- Queue-Based Fulfillment: Retry logic for failed operations
- Multi-Channel Support: Orders tracked with channel attribution
Architecture
System Components
┌─────────────────────────────────────────┐
│ Frontend Interfaces │
├─────────────────────────────────────────┤
│ Store Portal Admin Portal │
│ /store/letzshop /admin/letzshop │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ API Layer │
├─────────────────────────────────────────┤
│ /api/v1/store/letzshop/* │
│ /api/v1/admin/letzshop/* │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ Service Layer │
├─────────────────────────────────────────┤
│ LetzshopClient CredentialsService│
│ (GraphQL) (Encryption) │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ Data Layer │
├─────────────────────────────────────────┤
│ StoreLetzshopCredentials │
│ LetzshopOrder │
│ LetzshopFulfillmentQueue │
│ LetzshopSyncLog │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ Letzshop GraphQL API │
│ https://letzshop.lu/graphql │
└─────────────────────────────────────────┘
Data Flow
- Credentials Setup: Store/Admin stores encrypted API key
- Order Import: System fetches unconfirmed shipments from Letzshop
- Order Processing: Orders stored locally with Letzshop IDs
- Fulfillment: Store 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 store account on the platform
Step 1: Configure API Credentials
Via Store 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 store 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/store/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 store portal or API:
# Trigger order import
curl -X POST /api/v1/store/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 store confirmation |
confirmed |
Store 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 |
Product Exceptions
When importing orders from Letzshop, products are matched by GTIN. If a product is not found in the store's catalog, the system gracefully imports the order with a placeholder product and creates an exception record for resolution.
Exception Workflow
Import Order → Product not found by GTIN
│
▼
Create order with placeholder
+ Flag item: needs_product_match=True
+ Create OrderItemException record
│
▼
Exception appears in QC dashboard
│
┌───────────┴───────────┐
│ │
Resolve Ignore
(assign product) (with reason)
│ │
▼ ▼
Order can be confirmed Still blocks confirmation
Exception Types
| Type | Description |
|---|---|
product_not_found |
GTIN not in store's product catalog |
gtin_mismatch |
GTIN format issue |
duplicate_gtin |
Multiple products with same GTIN |
Exception Statuses
| Status | Description | Blocks Confirmation |
|---|---|---|
pending |
Awaiting resolution | Yes |
resolved |
Product assigned | No |
ignored |
Marked as ignored | Yes |
Important: Both pending and ignored exceptions block order confirmation to Letzshop.
Viewing Exceptions
Navigate to Marketplace > Letzshop > Exceptions tab to see all unmatched products.
The dashboard shows:
- Pending: Exceptions awaiting resolution
- Resolved: Exceptions that have been matched
- Ignored: Exceptions marked as ignored
- Orders Affected: Orders with at least one exception
Resolving Exceptions
Via Admin UI
- Navigate to Marketplace > Letzshop > Exceptions
- Click Resolve on the pending exception
- Search for the correct product by name, SKU, or GTIN
- Select the product and click Confirm
- Optionally check "Apply to all exceptions with this GTIN" for bulk resolution
Via API
# Resolve a single exception
curl -X POST /api/v1/admin/order-exceptions/{exception_id}/resolve \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"product_id": 123,
"notes": "Matched to correct product manually"
}'
# Bulk resolve all exceptions with same GTIN
curl -X POST /api/v1/admin/order-exceptions/bulk-resolve?store_id=1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"gtin": "4006381333931",
"product_id": 123,
"notes": "Product imported to catalog"
}'
Auto-Matching
When products are imported to the store catalog (via product sync or manual import), the system automatically:
- Collects GTINs of newly imported products
- Finds pending exceptions with matching GTINs
- Resolves them by assigning the new product
This happens during:
- Single product import (
copy_to_store_catalog) - Bulk marketplace sync
Exception Statistics
Get counts via API:
curl -X GET /api/v1/admin/order-exceptions/stats?store_id=1 \
-H "Authorization: Bearer $TOKEN"
Response:
{
"pending": 15,
"resolved": 42,
"ignored": 3,
"total": 60,
"orders_with_exceptions": 8
}
For more details, see Order Item Exception System.
Fulfillment Operations
Confirm Order
Confirm that you can fulfill the order:
# Confirm all inventory units in an order
curl -X POST /api/v1/store/letzshop/orders/{order_id}/confirm \
-H "Authorization: Bearer $TOKEN"
# Or confirm specific units
curl -X POST /api/v1/store/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/store/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/store/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.
Shipping and Tracking
The system captures shipping information from Letzshop and provides local shipping management features.
Letzshop Nomenclature
Letzshop uses specific terminology for order references:
| Term | Example | Description |
|---|---|---|
| Order Number | R532332163 |
Customer-facing order reference |
| Shipment Number | H74683403433 |
Carrier shipment ID for tracking |
| Hash ID | nvDv5RQEmCwbjo |
Internal Letzshop reference |
Order Fields
Orders imported from Letzshop include:
| Field | Description |
|---|---|
external_order_number |
Letzshop order number (e.g., R532332163) |
shipment_number |
Carrier shipment number (e.g., H74683403433) |
shipping_carrier |
Carrier code (greco, colissimo, xpresslogistics) |
tracking_number |
Tracking number (if available) |
tracking_url |
Full tracking URL |
Carrier Detection
The system automatically detects the carrier from Letzshop shipment data:
| Carrier | Code | Label URL Prefix |
|---|---|---|
| Greco | greco |
https://dispatchweb.fr/Tracky/Home/ |
| Colissimo | colissimo |
Configurable in settings |
| XpressLogistics | xpresslogistics |
Configurable in settings |
Mark as Shipped
Mark orders as shipped locally (does not sync to Letzshop):
curl -X POST /api/v1/admin/orders/{order_id}/ship \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tracking_number": "1Z999AA10123456784",
"tracking_url": "https://tracking.example.com/1Z999AA10123456784",
"shipping_carrier": "ups"
}'
Note: This updates the local order status to shipped and sets the shipped_at timestamp. It does not send anything to Letzshop API.
Download Shipping Label
Get the shipping label URL for an order:
curl -X GET /api/v1/admin/orders/{order_id}/shipping-label \
-H "Authorization: Bearer $TOKEN"
Response:
{
"shipment_number": "H74683403433",
"shipping_carrier": "greco",
"label_url": "https://dispatchweb.fr/Tracky/Home/H74683403433",
"tracking_number": null,
"tracking_url": null
}
The label URL is constructed from:
- Carrier label URL prefix (configured in Admin Settings)
- Shipment number from the order
Carrier Label Settings
Configure carrier label URL prefixes in Admin > Settings > Shipping:
| Setting | Default | Description |
|---|---|---|
| Greco Label URL | https://dispatchweb.fr/Tracky/Home/ |
Greco tracking/label prefix |
| Colissimo Label URL | (empty) | Colissimo tracking prefix |
| XpressLogistics Label URL | (empty) | XpressLogistics prefix |
The full label URL is: {prefix}{shipment_number}
Tracking Information
Letzshop does not expose Greco tracking information via API. The tracking URL visible in the Letzshop web UI is auto-generated by Letzshop using the dispatchweb.fr prefix.
For orders using Greco carrier:
- The shipment number (e.g.,
H74683403433) is captured during import - The tracking URL can be constructed:
https://dispatchweb.fr/Tracky/Home/{shipment_number} - Use the Download Label feature to get this URL
API Reference
Store Endpoints
Base path: /api/v1/store/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 | /stores |
List stores with Letzshop status |
| GET | /stores/{id}/credentials |
Get store credentials |
| POST | /stores/{id}/credentials |
Set store credentials |
| PATCH | /stores/{id}/credentials |
Update store credentials |
| DELETE | /stores/{id}/credentials |
Delete store credentials |
| POST | /stores/{id}/test |
Test store connection |
| POST | /test |
Test any API key |
| GET | /stores/{id}/orders |
List store's Letzshop orders |
| POST | /stores/{id}/sync |
Trigger sync for store |
Order Endpoints
Base path: /api/v1/admin/orders
| Method | Endpoint | Description |
|---|---|---|
| GET | `` | List orders (cross-store) |
| GET | /stats |
Get order statistics |
| GET | /stores |
Get stores with orders |
| GET | /{id} |
Get order details |
| PATCH | /{id}/status |
Update order status |
| POST | /{id}/ship |
Mark as shipped |
| GET | /{id}/shipping-label |
Get shipping label URL |
Exception Endpoints
Base path: /api/v1/admin/order-exceptions
| Method | Endpoint | Description |
|---|---|---|
| GET | `` | List exceptions |
| GET | /stats |
Get exception statistics |
| GET | /{id} |
Get exception details |
| POST | /{id}/resolve |
Resolve with product |
| POST | /{id}/ignore |
Mark as ignored |
| POST | /bulk-resolve |
Bulk resolve by GTIN |
Response Schemas
Credentials Response
{
"id": 1,
"store_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,
"store_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
StoreLetzshopCredentials
Stores encrypted API credentials per store.
class StoreLetzshopCredentials(Base):
__tablename__ = "store_letzshop_credentials"
id: int # Primary key
store_id: int # FK to stores (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
store_id: int # FK to stores
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
store_id: int # FK to stores
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
store_id: int # FK to stores
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
- Stores: Can only manage their own Letzshop integration
- Admins: Can manage any store'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/store/letzshop/logs \
-H "Authorization: Bearer $TOKEN"
Order Has Unresolved Exceptions
Symptoms: "Order has X unresolved exception(s)" error when confirming
Cause: Order contains items that couldn't be matched to products during import
Solutions:
- Navigate to Marketplace > Letzshop > Exceptions tab
- Find the pending exceptions for this order
- Either:
- Resolve: Assign the correct product from your catalog
- Ignore: Mark as ignored if product will never be matched (still blocks confirmation)
- Retry the confirmation after resolving all exceptions
Cannot Find Shipping Label
Symptoms: "Download Label" returns empty or no URL
Possible Causes:
- Shipment number not captured during import
- Carrier label URL prefix not configured
- Unknown carrier type
Solutions:
- Re-sync the order to capture shipment data
- Check Admin > Settings > Shipping for carrier URL prefixes
- Verify the order has a valid
shipping_carrierandshipment_number
Best Practices
For Stores
- 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 store status regularly via admin dashboard
- Assist stores with connection issues
- Monitor sync logs for platform-wide issues
- Set up alerts for failed syncs (optional)
Related Documentation
- Order Item Exception System
- Marketplace Integration (CSV Import)
- Store RBAC
- Admin Integration Guide
- Exception Handling
Version History
-
v1.2 (2025-12-20): Shipping & Tracking enhancements
- Added
shipment_number,shipping_carrier,tracking_urlfields to orders - Carrier detection from Letzshop shipment data (Greco, Colissimo, XpressLogistics)
- Mark as Shipped feature (local only, does not sync to Letzshop)
- Shipping label URL generation using configurable carrier prefixes
- Admin settings for carrier label URL prefixes
- Added
-
v1.1 (2025-12-20): Product Exception System
- Graceful order import when products not found by GTIN
- Placeholder product per store for unmatched items
- Exception tracking with pending/resolved/ignored statuses
- Confirmation blocking until exceptions resolved
- Auto-matching when products are imported
- Exceptions tab in admin Letzshop management page
- Bulk resolution by GTIN
-
v1.0 (2025-12-13): Initial Letzshop order integration
- GraphQL client for order import
- Encrypted credential storage
- Fulfillment operations (confirm, reject, tracking)
- Admin and store API endpoints
- Sync logging and queue management