docs: migrate module documentation to single source of truth
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>
This commit is contained in:
@@ -1,254 +1 @@
|
||||
# Email Settings Guide
|
||||
|
||||
This guide covers email configuration for both **stores** and **platform administrators**. The Orion platform uses a layered email system where stores manage their own email sending while the platform handles system-level communications.
|
||||
|
||||
## Overview
|
||||
|
||||
The email system has two distinct configurations:
|
||||
|
||||
| Aspect | Platform (Admin) | Store |
|
||||
|--------|-----------------|--------|
|
||||
| Purpose | System emails (billing, admin notifications) | Customer-facing emails (orders, marketing) |
|
||||
| Configuration | Environment variables (.env) + Database overrides | Database (per-store) |
|
||||
| Cost | Platform owner pays | Store pays |
|
||||
| Providers | SMTP, SendGrid, Mailgun, SES | SMTP (all tiers), Premium providers (Business+) |
|
||||
|
||||
---
|
||||
|
||||
## Store Email Settings
|
||||
|
||||
### Getting Started
|
||||
|
||||
As a store, you need to configure email settings to send emails to your customers. This includes order confirmations, shipping updates, and marketing emails.
|
||||
|
||||
#### Accessing Email Settings
|
||||
|
||||
1. Log in to your Store Dashboard
|
||||
2. Navigate to **Settings** from the sidebar
|
||||
3. Click on the **Email** tab
|
||||
|
||||
### Available Providers
|
||||
|
||||
| Provider | Tier Required | Best For |
|
||||
|----------|---------------|----------|
|
||||
| SMTP | All tiers | Standard email servers, most common |
|
||||
| SendGrid | Business+ | High-volume transactional emails |
|
||||
| Mailgun | Business+ | Developer-friendly API |
|
||||
| Amazon SES | Business+ | AWS ecosystem, cost-effective |
|
||||
|
||||
### Configuring SMTP
|
||||
|
||||
SMTP is available for all subscription tiers. Common SMTP providers include:
|
||||
- Gmail (smtp.gmail.com:587)
|
||||
- Microsoft 365 (smtp.office365.com:587)
|
||||
- Your hosting provider's SMTP server
|
||||
|
||||
**Required Fields:**
|
||||
- **From Email**: The sender email address (e.g., orders@yourstore.com)
|
||||
- **From Name**: The sender display name (e.g., "Your Store")
|
||||
- **SMTP Host**: Your SMTP server address
|
||||
- **SMTP Port**: Usually 587 (TLS) or 465 (SSL)
|
||||
- **SMTP Username**: Your login username
|
||||
- **SMTP Password**: Your login password
|
||||
- **Use TLS**: Enable for port 587 (recommended)
|
||||
- **Use SSL**: Enable for port 465
|
||||
|
||||
### Configuring Premium Providers (Business+)
|
||||
|
||||
If you have a Business or Enterprise subscription, you can use premium email providers:
|
||||
|
||||
#### SendGrid
|
||||
1. Create a SendGrid account at [sendgrid.com](https://sendgrid.com)
|
||||
2. Generate an API key
|
||||
3. Enter the API key in your store settings
|
||||
|
||||
#### Mailgun
|
||||
1. Create a Mailgun account at [mailgun.com](https://mailgun.com)
|
||||
2. Add and verify your domain
|
||||
3. Get your API key from the dashboard
|
||||
4. Enter the API key and domain in your settings
|
||||
|
||||
#### Amazon SES
|
||||
1. Set up SES in your AWS account
|
||||
2. Verify your sender domain/email
|
||||
3. Create IAM credentials with SES permissions
|
||||
4. Enter the access key, secret key, and region
|
||||
|
||||
### Verifying Your Configuration
|
||||
|
||||
After configuring your email settings:
|
||||
|
||||
1. Click **Save Settings**
|
||||
2. Enter a test email address in the **Test Email** field
|
||||
3. Click **Send Test**
|
||||
4. Check your inbox for the test email
|
||||
|
||||
If the test fails, check:
|
||||
- Your credentials are correct
|
||||
- Your IP/domain is not blocked
|
||||
- For Gmail: Allow "less secure apps" or use an app password
|
||||
|
||||
### Email Warning Banner
|
||||
|
||||
Until you configure and verify your email settings, you'll see a warning banner at the top of your dashboard. This ensures you don't forget to set up email before your store goes live.
|
||||
|
||||
---
|
||||
|
||||
## Platform Admin Email Settings
|
||||
|
||||
### Overview
|
||||
|
||||
Platform administrators can configure system-wide email settings for platform communications like:
|
||||
- Subscription billing notifications
|
||||
- Admin alerts
|
||||
- Platform-wide announcements
|
||||
|
||||
### Configuration Sources
|
||||
|
||||
Admin email settings support two configuration sources:
|
||||
|
||||
1. **Environment Variables (.env)** - Default configuration
|
||||
2. **Database Overrides** - Override .env via the admin UI
|
||||
|
||||
Database settings take priority over .env values.
|
||||
|
||||
### Accessing Admin Email Settings
|
||||
|
||||
1. Log in to the Admin Panel
|
||||
2. Navigate to **Settings**
|
||||
3. Click on the **Email** tab
|
||||
|
||||
### Viewing Current Configuration
|
||||
|
||||
The Email tab shows:
|
||||
- **Provider**: Current email provider (SMTP, SendGrid, etc.)
|
||||
- **From Email**: Sender email address
|
||||
- **From Name**: Sender display name
|
||||
- **Status**: Whether email is configured and enabled
|
||||
- **DB Overrides**: Whether database overrides are active
|
||||
|
||||
### Editing Settings
|
||||
|
||||
Click **Edit Settings** to modify the email configuration:
|
||||
|
||||
1. Select the email provider
|
||||
2. Enter the required credentials
|
||||
3. Configure enabled/debug flags
|
||||
4. Click **Save Email Settings**
|
||||
|
||||
### Resetting to .env Defaults
|
||||
|
||||
If you've made database overrides and want to revert to .env configuration:
|
||||
|
||||
1. Click **Reset to .env Defaults**
|
||||
2. Confirm the action
|
||||
|
||||
This removes all email settings from the database, reverting to .env values.
|
||||
|
||||
### Testing Configuration
|
||||
|
||||
1. Enter a test email address
|
||||
2. Click **Send Test**
|
||||
3. Check your inbox
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables Reference
|
||||
|
||||
For platform configuration via .env:
|
||||
|
||||
```env
|
||||
# Provider: smtp, sendgrid, mailgun, ses
|
||||
EMAIL_PROVIDER=smtp
|
||||
|
||||
# Sender identity
|
||||
EMAIL_FROM_ADDRESS=noreply@yourplatform.com
|
||||
EMAIL_FROM_NAME=Your Platform
|
||||
EMAIL_REPLY_TO=support@yourplatform.com
|
||||
|
||||
# Behavior
|
||||
EMAIL_ENABLED=true
|
||||
EMAIL_DEBUG=false
|
||||
|
||||
# SMTP Configuration
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-username
|
||||
SMTP_PASSWORD=your-password
|
||||
SMTP_USE_TLS=true
|
||||
SMTP_USE_SSL=false
|
||||
|
||||
# SendGrid
|
||||
SENDGRID_API_KEY=your-api-key
|
||||
|
||||
# Mailgun
|
||||
MAILGUN_API_KEY=your-api-key
|
||||
MAILGUN_DOMAIN=mg.yourdomain.com
|
||||
|
||||
# Amazon SES
|
||||
AWS_ACCESS_KEY_ID=your-access-key
|
||||
AWS_SECRET_ACCESS_KEY=your-secret-key
|
||||
AWS_REGION=eu-west-1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tier-Based Branding
|
||||
|
||||
The email system includes tier-based branding for store emails:
|
||||
|
||||
| Tier | Branding |
|
||||
|------|----------|
|
||||
| Essential | "Powered by Orion" footer |
|
||||
| Professional | "Powered by Orion" footer |
|
||||
| Business | No branding (white-label) |
|
||||
| Enterprise | No branding (white-label) |
|
||||
|
||||
Business and Enterprise tier stores get completely white-labeled emails with no Orion branding.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**"Email sending is disabled"**
|
||||
- Check that `EMAIL_ENABLED=true` in .env
|
||||
- Or enable it in the admin settings
|
||||
|
||||
**"Connection refused" on SMTP**
|
||||
- Verify SMTP host and port
|
||||
- Check firewall rules
|
||||
- Ensure TLS/SSL settings match your server
|
||||
|
||||
**"Authentication failed"**
|
||||
- Double-check username/password
|
||||
- For Gmail, use an App Password
|
||||
- For Microsoft 365, check MFA requirements
|
||||
|
||||
**"SendGrid error: 403"**
|
||||
- Verify your API key has Mail Send permissions
|
||||
- Check sender identity is verified
|
||||
|
||||
**Premium provider not available**
|
||||
- Upgrade to Business or Enterprise tier
|
||||
- Contact support if you have the right tier but can't access
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug mode to log emails instead of sending them:
|
||||
- Set `EMAIL_DEBUG=true` in .env
|
||||
- Or enable "Debug mode" in admin settings
|
||||
|
||||
Debug mode logs the email content to the server logs without actually sending.
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Never share API keys or passwords** in logs or frontend
|
||||
2. **Use environment variables** for sensitive credentials
|
||||
3. **Enable TLS** for SMTP connections
|
||||
4. **Verify sender domains** with your email provider
|
||||
5. **Monitor email logs** for delivery issues
|
||||
6. **Rotate credentials** periodically
|
||||
This document has moved to the messaging module docs: [Email Settings](../modules/messaging/email-settings.md)
|
||||
|
||||
@@ -1,287 +1 @@
|
||||
# Email Templates Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The Orion platform provides a comprehensive email template system that allows:
|
||||
|
||||
- **Platform Administrators**: Manage all email templates across the platform
|
||||
- **Stores**: Customize customer-facing emails with their own branding
|
||||
|
||||
This guide covers how to use the email template system from both perspectives.
|
||||
|
||||
---
|
||||
|
||||
## For Stores
|
||||
|
||||
### Accessing Email Templates
|
||||
|
||||
1. Log in to your store dashboard
|
||||
2. Navigate to **Settings** > **Email Templates** in the sidebar
|
||||
3. You'll see a list of all customizable email templates
|
||||
|
||||
### Understanding Template Status
|
||||
|
||||
Each template shows its customization status:
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| **Platform Default** | Using the standard Orion template |
|
||||
| **Customized** | You have created a custom version |
|
||||
| Language badges (green) | Languages where you have customizations |
|
||||
|
||||
### Customizing a Template
|
||||
|
||||
1. Click on any template to open the edit modal
|
||||
2. Select the language tab you want to customize (EN, FR, DE, LB)
|
||||
3. Edit the following fields:
|
||||
- **Subject**: The email subject line
|
||||
- **HTML Body**: The rich HTML content
|
||||
- **Plain Text Body**: Fallback for email clients that don't support HTML
|
||||
|
||||
4. Click **Save** to save your customization
|
||||
|
||||
### Template Variables
|
||||
|
||||
Templates use special variables that are automatically replaced with actual values. Common variables include:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `{{ customer_name }}` | Customer's first name |
|
||||
| `{{ order_number }}` | Order reference number |
|
||||
| `{{ store_name }}` | Your store name |
|
||||
| `{{ platform_name }}` | Platform name (Orion or your whitelabel name) |
|
||||
|
||||
Each template shows its available variables in the reference panel.
|
||||
|
||||
### Previewing Templates
|
||||
|
||||
Before saving, you can preview your template:
|
||||
|
||||
1. Click **Preview** in the edit modal
|
||||
2. A preview window shows how the email will look
|
||||
3. Sample data is used for all variables
|
||||
|
||||
### Testing Templates
|
||||
|
||||
To send a test email:
|
||||
|
||||
1. Click **Send Test Email** in the edit modal
|
||||
2. Enter your email address
|
||||
3. Click **Send**
|
||||
4. Check your inbox to see the actual email
|
||||
|
||||
### Reverting to Platform Default
|
||||
|
||||
If you want to remove your customization and use the platform default:
|
||||
|
||||
1. Open the template edit modal
|
||||
2. Click **Revert to Default**
|
||||
3. Confirm the action
|
||||
|
||||
Your customization will be deleted and the platform template will be used.
|
||||
|
||||
### Available Templates for Stores
|
||||
|
||||
| Template | Category | Description |
|
||||
|----------|----------|-------------|
|
||||
| Welcome Email | AUTH | Sent when a customer registers |
|
||||
| Password Reset | AUTH | Password reset link |
|
||||
| Order Confirmation | ORDERS | Sent after order placement |
|
||||
| Shipping Notification | ORDERS | Sent when order is shipped |
|
||||
|
||||
**Note:** Billing and subscription emails are platform-only and cannot be customized.
|
||||
|
||||
---
|
||||
|
||||
## For Platform Administrators
|
||||
|
||||
### Accessing Email Templates
|
||||
|
||||
1. Log in to the admin dashboard
|
||||
2. Navigate to **System** > **Email Templates** in the sidebar
|
||||
3. You'll see all platform templates grouped by category
|
||||
|
||||
### Template Categories
|
||||
|
||||
| Category | Description | Store Override |
|
||||
|----------|-------------|-----------------|
|
||||
| AUTH | Authentication emails | Allowed |
|
||||
| ORDERS | Order-related emails | Allowed |
|
||||
| BILLING | Subscription/payment emails | **Not Allowed** |
|
||||
| SYSTEM | System notifications | Allowed |
|
||||
| MARKETING | Promotional emails | Allowed |
|
||||
|
||||
### Editing Platform Templates
|
||||
|
||||
1. Click on any template to open the edit modal
|
||||
2. Select the language tab (EN, FR, DE, LB)
|
||||
3. Edit the subject and body content
|
||||
4. Click **Save**
|
||||
|
||||
**Important:** Changes to platform templates affect:
|
||||
- All stores who haven't customized the template
|
||||
- New stores automatically
|
||||
|
||||
### Creating New Templates
|
||||
|
||||
To add a new template:
|
||||
|
||||
1. Use the database seed script or migration
|
||||
2. Define the template code, category, and languages
|
||||
3. Set `is_platform_only` if stores shouldn't override it
|
||||
|
||||
### Viewing Email Logs
|
||||
|
||||
To see email delivery history:
|
||||
|
||||
1. Open a template
|
||||
2. Click **View Logs**
|
||||
3. See recent emails sent using this template
|
||||
|
||||
Logs show:
|
||||
- Recipient email
|
||||
- Send date/time
|
||||
- Delivery status
|
||||
- Store (if applicable)
|
||||
|
||||
### Template Best Practices
|
||||
|
||||
1. **Use all 4 languages**: Provide content in EN, FR, DE, and LB
|
||||
2. **Test before publishing**: Always send test emails
|
||||
3. **Include plain text**: Not all email clients support HTML
|
||||
4. **Use consistent branding**: Follow Orion brand guidelines
|
||||
5. **Keep subjects short**: Under 60 characters for mobile
|
||||
|
||||
---
|
||||
|
||||
## Language Resolution
|
||||
|
||||
When sending an email, the system determines the language in this order:
|
||||
|
||||
1. **Customer's preferred language** (if set in their profile)
|
||||
2. **Store's storefront language** (if customer doesn't have preference)
|
||||
3. **Platform default** (French - "fr")
|
||||
|
||||
### Template Resolution for Stores
|
||||
|
||||
1. System checks if store has a custom override
|
||||
2. If yes, uses store's template
|
||||
3. If no, falls back to platform template
|
||||
4. If requested language unavailable, falls back to English
|
||||
|
||||
---
|
||||
|
||||
## Branding
|
||||
|
||||
### Standard Stores
|
||||
|
||||
Standard stores' emails include Orion branding:
|
||||
- Orion logo in header
|
||||
- "Powered by Orion" footer
|
||||
|
||||
### Whitelabel Stores
|
||||
|
||||
Enterprise-tier stores with whitelabel enabled:
|
||||
- No Orion branding
|
||||
- Store's logo in header
|
||||
- Custom footer (if configured)
|
||||
|
||||
---
|
||||
|
||||
## Email Template Variables Reference
|
||||
|
||||
### Authentication Templates
|
||||
|
||||
#### signup_welcome
|
||||
```
|
||||
{{ first_name }} - Customer's first name
|
||||
{{ merchant_name }} - Store merchant name
|
||||
{{ email }} - Customer's email
|
||||
{{ login_url }} - Link to login page
|
||||
{{ trial_days }} - Trial period length
|
||||
{{ tier_name }} - Subscription tier
|
||||
```
|
||||
|
||||
#### password_reset
|
||||
```
|
||||
{{ customer_name }} - Customer's name
|
||||
{{ reset_link }} - Password reset URL
|
||||
{{ expiry_hours }} - Link expiration time
|
||||
```
|
||||
|
||||
### Order Templates
|
||||
|
||||
#### order_confirmation
|
||||
```
|
||||
{{ customer_name }} - Customer's name
|
||||
{{ order_number }} - Order reference
|
||||
{{ order_total }} - Order total amount
|
||||
{{ order_items_count }} - Number of items
|
||||
{{ order_date }} - Order date
|
||||
{{ shipping_address }} - Delivery address
|
||||
```
|
||||
|
||||
### Common Variables (All Templates)
|
||||
|
||||
```
|
||||
{{ platform_name }} - "Orion" or whitelabel name
|
||||
{{ platform_logo_url }} - Platform logo URL
|
||||
{{ support_email }} - Support email address
|
||||
{{ store_name }} - Store's business name
|
||||
{{ store_logo_url }} - Store's logo URL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Email Not Received
|
||||
|
||||
1. Check spam/junk folder
|
||||
2. Verify email address is correct
|
||||
3. Check email logs in admin dashboard
|
||||
4. Verify SMTP configuration
|
||||
|
||||
### Template Not Applying
|
||||
|
||||
1. Clear browser cache
|
||||
2. Verify the correct language is selected
|
||||
3. Check if store override exists
|
||||
4. Verify template is not platform-only
|
||||
|
||||
### Variables Not Replaced
|
||||
|
||||
1. Check variable spelling (case-sensitive)
|
||||
2. Ensure variable is available for this template
|
||||
3. Wrap variables in `{{ }}` syntax
|
||||
4. Check for typos in variable names
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
For developers integrating with the email system:
|
||||
|
||||
### Sending a Template Email
|
||||
|
||||
```python
|
||||
from app.services.email_service import EmailService
|
||||
|
||||
email_service = EmailService(db)
|
||||
email_service.send_template(
|
||||
template_code="order_confirmation",
|
||||
to_email="customer@example.com",
|
||||
to_name="John Doe",
|
||||
language="fr",
|
||||
variables={
|
||||
"customer_name": "John",
|
||||
"order_number": "ORD-12345",
|
||||
"order_total": "99.99",
|
||||
},
|
||||
store_id=store.id,
|
||||
related_type="order",
|
||||
related_id=order.id,
|
||||
)
|
||||
```
|
||||
|
||||
See [Email Templates Architecture](../implementation/email-templates-architecture.md) for full technical documentation.
|
||||
This document has moved to the CMS module docs: [Email Templates Guide](../modules/cms/email-templates-guide.md)
|
||||
|
||||
@@ -1,366 +1 @@
|
||||
# Inventory Management
|
||||
|
||||
## Overview
|
||||
|
||||
The Orion platform provides comprehensive inventory management with support for:
|
||||
|
||||
- **Multi-location tracking** - Track stock across warehouses, stores, and storage bins
|
||||
- **Reservation system** - Reserve items for pending orders
|
||||
- **Digital products** - Automatic unlimited inventory for digital goods
|
||||
- **Admin operations** - Manage inventory on behalf of stores
|
||||
|
||||
---
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Storage Locations
|
||||
|
||||
Inventory is tracked at the **storage location level**. Each product can have stock in multiple locations:
|
||||
|
||||
```
|
||||
Product: "Wireless Headphones"
|
||||
├── WAREHOUSE_MAIN: 100 units (10 reserved)
|
||||
├── WAREHOUSE_WEST: 50 units (0 reserved)
|
||||
└── STORE_FRONT: 25 units (5 reserved)
|
||||
|
||||
Total: 175 units | Reserved: 15 | Available: 160
|
||||
```
|
||||
|
||||
**Location naming:** Locations are text strings, normalized to UPPERCASE (e.g., `WAREHOUSE_A`, `STORE_01`).
|
||||
|
||||
### Inventory States
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `quantity` | Total physical stock at location |
|
||||
| `reserved_quantity` | Items reserved for pending orders |
|
||||
| `available_quantity` | `quantity - reserved_quantity` (can be sold) |
|
||||
|
||||
### Product Types & Inventory
|
||||
|
||||
| Product Type | Inventory Behavior |
|
||||
|--------------|-------------------|
|
||||
| **Physical** | Requires inventory tracking, orders check available stock |
|
||||
| **Digital** | **Unlimited inventory** - no stock constraints |
|
||||
| **Service** | Treated as digital (unlimited) |
|
||||
| **Subscription** | Treated as digital (unlimited) |
|
||||
|
||||
---
|
||||
|
||||
## Digital Products
|
||||
|
||||
Digital products have **unlimited inventory** by default. This means:
|
||||
|
||||
- Orders for digital products never fail due to "insufficient inventory"
|
||||
- No need to create inventory entries for digital products
|
||||
- The `available_inventory` property returns `999999` (effectively unlimited)
|
||||
|
||||
### How It Works
|
||||
|
||||
```python
|
||||
# In Product model
|
||||
@property
|
||||
def has_unlimited_inventory(self) -> bool:
|
||||
"""Digital products have unlimited inventory."""
|
||||
return self.is_digital
|
||||
|
||||
@property
|
||||
def available_inventory(self) -> int:
|
||||
"""Calculate available inventory."""
|
||||
if self.has_unlimited_inventory:
|
||||
return 999999 # Unlimited
|
||||
return sum(inv.available_quantity for inv in self.inventory_entries)
|
||||
```
|
||||
|
||||
### Setting a Product as Digital
|
||||
|
||||
Digital products are identified by the `is_digital` flag on the `MarketplaceProduct`:
|
||||
|
||||
```python
|
||||
marketplace_product.is_digital = True
|
||||
marketplace_product.product_type_enum = "digital"
|
||||
marketplace_product.digital_delivery_method = "license_key" # or "download", "email"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Inventory Operations
|
||||
|
||||
### Set Inventory
|
||||
|
||||
Replace the exact quantity at a location:
|
||||
|
||||
```http
|
||||
POST /api/v1/store/inventory/set
|
||||
{
|
||||
"product_id": 123,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 100
|
||||
}
|
||||
```
|
||||
|
||||
### Adjust Inventory
|
||||
|
||||
Add or remove stock (positive = add, negative = remove):
|
||||
|
||||
```http
|
||||
POST /api/v1/store/inventory/adjust
|
||||
{
|
||||
"product_id": 123,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": -10 // Remove 10 units
|
||||
}
|
||||
```
|
||||
|
||||
### Reserve Inventory
|
||||
|
||||
Mark items as reserved for an order:
|
||||
|
||||
```http
|
||||
POST /api/v1/store/inventory/reserve
|
||||
{
|
||||
"product_id": 123,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 5
|
||||
}
|
||||
```
|
||||
|
||||
### Release Reservation
|
||||
|
||||
Cancel a reservation (order cancelled):
|
||||
|
||||
```http
|
||||
POST /api/v1/store/inventory/release
|
||||
{
|
||||
"product_id": 123,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 5
|
||||
}
|
||||
```
|
||||
|
||||
### Fulfill Reservation
|
||||
|
||||
Complete an order (items shipped):
|
||||
|
||||
```http
|
||||
POST /api/v1/store/inventory/fulfill
|
||||
{
|
||||
"product_id": 123,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 5
|
||||
}
|
||||
```
|
||||
|
||||
This decreases both `quantity` and `reserved_quantity`.
|
||||
|
||||
---
|
||||
|
||||
## Reservation Workflow
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Order Created │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Reserve Items │ reserved_quantity += order_qty
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────┐ ┌──────────┐
|
||||
│Cancel │ │ Ship │
|
||||
└───┬───┘ └────┬─────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────┐ ┌──────────────┐
|
||||
│ Release │ │ Fulfill │
|
||||
│reserved │ │ quantity -= │
|
||||
│ -= qty │ │ reserved -= │
|
||||
└─────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Inventory Management
|
||||
|
||||
Administrators can manage inventory on behalf of any store through the admin UI at `/admin/inventory` or via the API.
|
||||
|
||||
### Admin UI Features
|
||||
|
||||
The admin inventory page provides:
|
||||
|
||||
- **Overview Statistics** - Total entries, stock quantities, reserved items, and low stock alerts
|
||||
- **Filtering** - Filter by store, location, and low stock threshold
|
||||
- **Search** - Search by product title or SKU
|
||||
- **Stock Adjustment** - Add or remove stock with optional reason tracking
|
||||
- **Set Quantity** - Set exact stock quantity at any location
|
||||
- **Delete Entries** - Remove inventory entries
|
||||
|
||||
### Admin API Endpoints
|
||||
|
||||
### List All Inventory
|
||||
|
||||
```http
|
||||
GET /api/v1/admin/inventory
|
||||
GET /api/v1/admin/inventory?store_id=1
|
||||
GET /api/v1/admin/inventory?low_stock=10
|
||||
```
|
||||
|
||||
### Get Inventory Statistics
|
||||
|
||||
```http
|
||||
GET /api/v1/admin/inventory/stats
|
||||
|
||||
Response:
|
||||
{
|
||||
"total_entries": 150,
|
||||
"total_quantity": 5000,
|
||||
"total_reserved": 200,
|
||||
"total_available": 4800,
|
||||
"low_stock_count": 12,
|
||||
"stores_with_inventory": 5,
|
||||
"unique_locations": 8
|
||||
}
|
||||
```
|
||||
|
||||
### Low Stock Alerts
|
||||
|
||||
```http
|
||||
GET /api/v1/admin/inventory/low-stock?threshold=10
|
||||
|
||||
Response:
|
||||
[
|
||||
{
|
||||
"product_id": 123,
|
||||
"store_name": "TechStore",
|
||||
"product_title": "USB Cable",
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 3,
|
||||
"available_quantity": 2
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Set Inventory (Admin)
|
||||
|
||||
```http
|
||||
POST /api/v1/admin/inventory/set
|
||||
{
|
||||
"store_id": 1,
|
||||
"product_id": 123,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 100
|
||||
}
|
||||
```
|
||||
|
||||
### Adjust Inventory (Admin)
|
||||
|
||||
```http
|
||||
POST /api/v1/admin/inventory/adjust
|
||||
{
|
||||
"store_id": 1,
|
||||
"product_id": 123,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 25,
|
||||
"reason": "Restocking from supplier"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Inventory Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE inventory (
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_id INTEGER NOT NULL REFERENCES products(id),
|
||||
store_id INTEGER NOT NULL REFERENCES stores(id),
|
||||
location VARCHAR NOT NULL,
|
||||
quantity INTEGER NOT NULL DEFAULT 0,
|
||||
reserved_quantity INTEGER DEFAULT 0,
|
||||
gtin VARCHAR,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
|
||||
UNIQUE(product_id, location)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_inventory_store_product ON inventory(store_id, product_id);
|
||||
CREATE INDEX idx_inventory_product_location ON inventory(product_id, location);
|
||||
```
|
||||
|
||||
### Constraints
|
||||
|
||||
- **Unique constraint:** `(product_id, location)` - One entry per product/location
|
||||
- **Foreign keys:** References `products` and `stores` tables
|
||||
- **Non-negative:** `quantity` and `reserved_quantity` must be >= 0
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Physical Products
|
||||
|
||||
1. **Create inventory entries** before accepting orders
|
||||
2. **Use meaningful location names** (e.g., `WAREHOUSE_MAIN`, `STORE_NYC`)
|
||||
3. **Monitor low stock** using the admin dashboard or API
|
||||
4. **Reserve on order creation** to prevent overselling
|
||||
|
||||
### Digital Products
|
||||
|
||||
1. **No inventory setup needed** - unlimited by default
|
||||
2. **Optional:** Create entries for license key tracking
|
||||
3. **Focus on fulfillment** - digital delivery mechanism
|
||||
|
||||
### Multi-Location
|
||||
|
||||
1. **Aggregate queries** use `Product.total_inventory` and `Product.available_inventory`
|
||||
2. **Location-specific** operations use the Inventory model directly
|
||||
3. **Transfers** between locations: adjust down at source, adjust up at destination
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Store Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/api/v1/store/inventory/set` | Set exact quantity |
|
||||
| POST | `/api/v1/store/inventory/adjust` | Add/remove quantity |
|
||||
| POST | `/api/v1/store/inventory/reserve` | Reserve for order |
|
||||
| POST | `/api/v1/store/inventory/release` | Cancel reservation |
|
||||
| POST | `/api/v1/store/inventory/fulfill` | Complete order |
|
||||
| GET | `/api/v1/store/inventory/product/{id}` | Product summary |
|
||||
| GET | `/api/v1/store/inventory` | List with filters |
|
||||
| PUT | `/api/v1/store/inventory/{id}` | Update entry |
|
||||
| DELETE | `/api/v1/store/inventory/{id}` | Delete entry |
|
||||
|
||||
### Admin Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/v1/admin/inventory` | List all (cross-store) |
|
||||
| GET | `/api/v1/admin/inventory/stats` | Platform statistics |
|
||||
| GET | `/api/v1/admin/inventory/low-stock` | Low stock alerts |
|
||||
| GET | `/api/v1/admin/inventory/stores` | Stores with inventory |
|
||||
| GET | `/api/v1/admin/inventory/locations` | Unique locations |
|
||||
| GET | `/api/v1/admin/inventory/stores/{id}` | Store inventory |
|
||||
| GET | `/api/v1/admin/inventory/products/{id}` | Product summary |
|
||||
| POST | `/api/v1/admin/inventory/set` | Set (requires store_id) |
|
||||
| POST | `/api/v1/admin/inventory/adjust` | Adjust (requires store_id) |
|
||||
| PUT | `/api/v1/admin/inventory/{id}` | Update entry |
|
||||
| DELETE | `/api/v1/admin/inventory/{id}` | Delete entry |
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Product Management](product-management.md)
|
||||
- [Admin Inventory Migration Plan](../implementation/inventory-admin-migration.md)
|
||||
- [Store Operations Expansion](../development/migration/store-operations-expansion.md)
|
||||
This document has moved to the inventory module docs: [User Guide](../modules/inventory/user-guide.md)
|
||||
|
||||
@@ -1,261 +1,3 @@
|
||||
# Letzshop Admin Management Guide
|
||||
|
||||
Complete guide for managing Letzshop integration from the Admin Portal at `/admin/marketplace/letzshop`.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Store Selection](#store-selection)
|
||||
- [Products Tab](#products-tab)
|
||||
- [Orders Tab](#orders-tab)
|
||||
- [Exceptions Tab](#exceptions-tab)
|
||||
- [Jobs Tab](#jobs-tab)
|
||||
- [Settings Tab](#settings-tab)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Letzshop Management page provides a unified interface for managing Letzshop marketplace integration for all stores. Key features:
|
||||
|
||||
- **Multi-Store Support**: Select any store to manage their Letzshop integration
|
||||
- **Product Management**: View, import, and export products
|
||||
- **Order Processing**: View orders, confirm inventory, set tracking
|
||||
- **Exception Handling**: Resolve product matching exceptions
|
||||
- **Job Monitoring**: Track import, export, and sync operations
|
||||
- **Configuration**: Manage CSV URLs, credentials, and sync settings
|
||||
|
||||
---
|
||||
|
||||
## Store Selection
|
||||
|
||||
At the top of the page, use the store autocomplete to select which store to manage:
|
||||
|
||||
1. Type to search for a store by name or code
|
||||
2. Select from the dropdown
|
||||
3. The page loads store-specific data for all tabs
|
||||
4. Your selection is saved and restored on next visit
|
||||
|
||||
**Cross-Store View**: When no store is selected, the Orders and Exceptions tabs show data across all stores.
|
||||
|
||||
---
|
||||
|
||||
## Products Tab
|
||||
|
||||
The Products tab displays Letzshop marketplace products imported for the selected store.
|
||||
|
||||
### Product Listing
|
||||
|
||||
- **Search**: Filter by title, GTIN, SKU, or brand
|
||||
- **Status Filter**: Show all, active only, or inactive only
|
||||
- **Pagination**: Navigate through product pages
|
||||
|
||||
### Product Table Columns
|
||||
|
||||
| Column | Description |
|
||||
|--------|-------------|
|
||||
| Product | Image, title, and brand |
|
||||
| Identifiers | GTIN and SKU codes |
|
||||
| Price | Product price with currency |
|
||||
| Status | Active/Inactive badge |
|
||||
| Actions | View product details |
|
||||
|
||||
### Import Products
|
||||
|
||||
Click the **Import** button to open the import modal:
|
||||
|
||||
1. **Import Single Language**: Select a language and enter the CSV URL
|
||||
2. **Import All Languages**: Imports from all configured CSV URLs (FR, DE, EN)
|
||||
|
||||
Import settings (batch size) are configured in the Settings tab.
|
||||
|
||||
### Export Products
|
||||
|
||||
Click the **Export** button to export products to the Letzshop pickup folder:
|
||||
|
||||
- Exports all three languages (FR, DE, EN) automatically
|
||||
- Files are placed in `exports/letzshop/{store_code}/`
|
||||
- Filename format: `{store_code}_products_{language}.csv`
|
||||
- The export is logged and appears in the Jobs tab
|
||||
|
||||
Export settings (include inactive products) are configured in the Settings tab.
|
||||
|
||||
---
|
||||
|
||||
## Orders Tab
|
||||
|
||||
The Orders tab displays orders from Letzshop for the selected store (or all stores if none selected).
|
||||
|
||||
### Order Listing
|
||||
|
||||
- **Search**: Filter by order number, customer name, or email
|
||||
- **Status Filter**: All, Pending, Confirmed, Shipped, Declined
|
||||
- **Date Range**: Filter by order date
|
||||
|
||||
### Order Actions
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| View | Open order details modal |
|
||||
| Confirm | Confirm all items in order |
|
||||
| Decline | Decline all items in order |
|
||||
| Set Tracking | Add tracking number and carrier |
|
||||
|
||||
### Order Details Modal
|
||||
|
||||
Shows complete order information including:
|
||||
|
||||
- Order number and date
|
||||
- Customer name and email
|
||||
- Shipping address
|
||||
- Order items with confirmation status
|
||||
- Tracking information (if set)
|
||||
|
||||
---
|
||||
|
||||
## Exceptions Tab
|
||||
|
||||
The Exceptions tab shows product matching exceptions that need resolution. See the [Order Item Exceptions documentation](../implementation/order-item-exceptions.md) for details.
|
||||
|
||||
### Exception Types
|
||||
|
||||
When an order is imported and a product cannot be matched by GTIN:
|
||||
|
||||
1. The order is imported with a placeholder product
|
||||
2. An exception is created for resolution
|
||||
3. The order cannot be confirmed until exceptions are resolved
|
||||
|
||||
### Resolution Actions
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| Resolve | Assign the correct product to the order item |
|
||||
| Bulk Resolve | Resolve all exceptions for the same GTIN |
|
||||
| Ignore | Mark as ignored (still blocks confirmation) |
|
||||
|
||||
---
|
||||
|
||||
## Jobs Tab
|
||||
|
||||
The Jobs tab provides a unified view of all Letzshop-related operations for the selected store.
|
||||
|
||||
### Job Types
|
||||
|
||||
| Type | Icon | Color | Description |
|
||||
|------|------|-------|-------------|
|
||||
| Product Import | Cloud Download | Purple | Importing products from Letzshop CSV |
|
||||
| Product Export | Cloud Upload | Blue | Exporting products to pickup folder |
|
||||
| Historical Import | Clock | Orange | Importing historical orders |
|
||||
| Order Sync | Refresh | Indigo | Syncing orders from Letzshop API |
|
||||
|
||||
### Job Information
|
||||
|
||||
Each job displays:
|
||||
|
||||
- **ID**: Unique job identifier
|
||||
- **Type**: Import, Export, Historical Import, or Order Sync
|
||||
- **Status**: Pending, Processing, Completed, Failed, or Partial
|
||||
- **Records**: Success count / Total processed (failed count if any)
|
||||
- **Started**: When the job began
|
||||
- **Duration**: How long the job took
|
||||
|
||||
#### Records Column Meaning
|
||||
|
||||
| Job Type | Records Shows |
|
||||
|----------|---------------|
|
||||
| Product Import | Products imported / Total products |
|
||||
| Product Export | Files exported / Total files (3 languages) |
|
||||
| Historical Import | Orders imported / Total orders |
|
||||
| Order Sync | Orders synced / Total orders |
|
||||
|
||||
### Filtering
|
||||
|
||||
- **Type Filter**: Show specific job types
|
||||
- **Status Filter**: Show jobs with specific status
|
||||
|
||||
### Job Actions
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| View Errors | Show error details (for failed jobs) |
|
||||
| View Details | Show complete job information |
|
||||
|
||||
---
|
||||
|
||||
## Settings Tab
|
||||
|
||||
The Settings tab manages Letzshop integration configuration for the selected store.
|
||||
|
||||
### CSV Feed URLs
|
||||
|
||||
Configure the URLs for Letzshop product CSV feeds:
|
||||
|
||||
- **French (FR)**: URL for French product data
|
||||
- **German (DE)**: URL for German product data
|
||||
- **English (EN)**: URL for English product data
|
||||
|
||||
### Import Settings
|
||||
|
||||
- **Batch Size**: Number of products to process per batch (100-5000)
|
||||
|
||||
### Export Settings
|
||||
|
||||
- **Include Inactive**: Whether to include inactive products in exports
|
||||
|
||||
### API Credentials
|
||||
|
||||
Configure Letzshop API access:
|
||||
|
||||
- **API Key**: Your Letzshop API key (encrypted at rest)
|
||||
- **Test Connection**: Verify API connectivity
|
||||
|
||||
### Sync Settings
|
||||
|
||||
- **Auto-Sync Enabled**: Enable automatic order synchronization
|
||||
- **Sync Interval**: How often to sync orders (in minutes)
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Products
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/admin/products` | GET | List marketplace products with filters |
|
||||
| `/admin/products/stats` | GET | Get product statistics |
|
||||
| `/admin/letzshop/stores/{id}/export` | GET | Download CSV export |
|
||||
| `/admin/letzshop/stores/{id}/export` | POST | Export to pickup folder |
|
||||
|
||||
### Jobs
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/admin/letzshop/stores/{id}/jobs` | GET | List jobs for store |
|
||||
| `/admin/marketplace-import-jobs` | POST | Create import job |
|
||||
|
||||
### Orders
|
||||
|
||||
See [Letzshop Order Integration](letzshop-order-integration.md) for complete order API documentation.
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Product Management
|
||||
|
||||
1. **Regular Imports**: Schedule regular imports to keep product data current
|
||||
2. **Export Before Sync**: Export products before Letzshop's pickup schedule
|
||||
3. **Monitor Jobs**: Check the Jobs tab for failed imports/exports
|
||||
|
||||
### Order Processing
|
||||
|
||||
1. **Check Exceptions First**: Resolve exceptions before confirming orders
|
||||
2. **Verify Tracking**: Ensure tracking numbers are valid before submission
|
||||
3. **Monitor Sync Status**: Check for failed order syncs in Jobs tab
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
1. **Products Not Appearing**: Verify CSV URL is accessible and valid
|
||||
2. **Export Failed**: Check write permissions on exports directory
|
||||
3. **Orders Not Syncing**: Verify API credentials and test connection
|
||||
This document has moved to the marketplace module docs: [Admin Guide](../modules/marketplace/admin-guide.md)
|
||||
|
||||
@@ -1,322 +1,3 @@
|
||||
# Letzshop Marketplace Integration
|
||||
|
||||
## Introduction
|
||||
|
||||
This guide covers the Orion platform's integration with the Letzshop marketplace, including:
|
||||
|
||||
- **Product Export**: Generate Letzshop-compatible CSV files from your product catalog
|
||||
- **Order Import**: Fetch and manage orders from Letzshop
|
||||
- **Fulfillment Sync**: Confirm/reject orders, set tracking numbers
|
||||
- **GraphQL API Reference**: Direct API access for advanced use cases
|
||||
|
||||
---
|
||||
|
||||
## Product Export
|
||||
|
||||
### Overview
|
||||
|
||||
The Orion platform can export your products to Letzshop-compatible CSV format (Google Shopping feed format). This allows you to:
|
||||
|
||||
- Upload your product catalog to Letzshop marketplace
|
||||
- Generate feeds in multiple languages (English, French, German)
|
||||
- Include all required Letzshop fields automatically
|
||||
|
||||
### API Endpoints
|
||||
|
||||
#### Store Export
|
||||
|
||||
```http
|
||||
GET /api/v1/store/letzshop/export?language=fr
|
||||
Authorization: Bearer <store_token>
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `language` | string | `en` | Language for title/description (`en`, `fr`, `de`) |
|
||||
| `include_inactive` | bool | `false` | Include inactive products |
|
||||
|
||||
**Response:** CSV file download (`store_code_letzshop_export.csv`)
|
||||
|
||||
#### Admin Export
|
||||
|
||||
```http
|
||||
GET /api/v1/admin/letzshop/export?store_id=1&language=fr
|
||||
Authorization: Bearer <admin_token>
|
||||
```
|
||||
|
||||
**Additional Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `store_id` | int | Required. Store ID to export |
|
||||
|
||||
### CSV Format
|
||||
|
||||
The export generates a tab-separated CSV file with these columns:
|
||||
|
||||
| Column | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `id` | Product SKU | `PROD-001` |
|
||||
| `title` | Product title (localized) | `Wireless Headphones` |
|
||||
| `description` | Product description (localized) | `High-quality...` |
|
||||
| `link` | Product URL | `https://shop.example.com/product/123` |
|
||||
| `image_link` | Main product image | `https://cdn.example.com/img.jpg` |
|
||||
| `additional_image_link` | Additional images (comma-separated) | `img2.jpg,img3.jpg` |
|
||||
| `availability` | Stock status | `in stock` / `out of stock` |
|
||||
| `price` | Regular price with currency | `49.99 EUR` |
|
||||
| `sale_price` | Sale price with currency | `39.99 EUR` |
|
||||
| `brand` | Brand name | `TechBrand` |
|
||||
| `gtin` | Global Trade Item Number | `0012345678901` |
|
||||
| `mpn` | Manufacturer Part Number | `TB-WH-001` |
|
||||
| `google_product_category` | Google category ID | `Electronics > Audio` |
|
||||
| `condition` | Product condition | `new` / `used` / `refurbished` |
|
||||
| `atalanda:tax_rate` | Luxembourg VAT rate | `17` |
|
||||
|
||||
### Multi-Language Support
|
||||
|
||||
Products are exported with localized content based on the `language` parameter:
|
||||
|
||||
```bash
|
||||
# French export
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://api.example.com/api/v1/store/letzshop/export?language=fr"
|
||||
|
||||
# German export
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://api.example.com/api/v1/store/letzshop/export?language=de"
|
||||
|
||||
# English export (default)
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://api.example.com/api/v1/store/letzshop/export?language=en"
|
||||
```
|
||||
|
||||
If a translation is not available for the requested language, the system falls back to English, then to any available translation.
|
||||
|
||||
### Using the Export
|
||||
|
||||
1. **Navigate to Letzshop** in your store dashboard
|
||||
2. **Click the Export tab**
|
||||
3. **Select your language** (French, German, or English)
|
||||
4. **Click "Download CSV"**
|
||||
5. **Upload to Letzshop** via their merchant portal
|
||||
|
||||
---
|
||||
|
||||
## Order Integration
|
||||
|
||||
For details on order import and fulfillment, see [Letzshop Order Integration](./letzshop-order-integration.md).
|
||||
|
||||
---
|
||||
|
||||
## Letzshop GraphQL API Reference
|
||||
|
||||
The following sections document the Letzshop GraphQL API for direct integration.
|
||||
|
||||
---
|
||||
|
||||
## GraphQL API
|
||||
|
||||
Utilizing this API, you can retrieve and modify data on products, stores, and shipments. Letzshop uses GraphQL, allowing for precise queries.
|
||||
|
||||
**Endpoint**:
|
||||
http://letzshop.lu/graphql
|
||||
Replace YOUR_API_ACCESS_KEY with your actual key or remove the Authorization header for public data.
|
||||
|
||||
## Authentication
|
||||
|
||||
Some data is public (e.g., store description and product prices).
|
||||
For protected operations (e.g., orders or vouchers), an API key is required.
|
||||
|
||||
Request one via your account manager or email: support@letzshop.lu
|
||||
|
||||
|
||||
Include the key:
|
||||
|
||||
|
||||
Authorization: Bearer YOUR_API_ACCESS_KEY
|
||||
|
||||
---
|
||||
|
||||
## Playground
|
||||
|
||||
- Access the interactive GraphQL Playground via the Letzshop website.
|
||||
- It provides live docs, auto-complete (CTRL + Space), and run-time testing.
|
||||
- **Caution**: You're working on production—mutations like confirming orders are real. [1](https://letzshop.lu/en/dev)
|
||||
|
||||
---
|
||||
|
||||
## Deprecations
|
||||
|
||||
The following GraphQL fields will be removed soon:
|
||||
|
||||
| Type | Field | Replacement |
|
||||
|---------|---------------------|----------------------------------|
|
||||
| Event | latitude, longitude | `#lat`, `#lng` |
|
||||
| Greco | weight | `packages.weight` |
|
||||
| Product | ageRestriction | `_age_restriction` (int) |
|
||||
| Taxon | identifier | `slug` |
|
||||
| User | billAddress, shipAddress | on `orders` instead |
|
||||
| Store | facebookLink, instagramLink, twitterLink, youtubeLink | `social_media_links` |
|
||||
| Store | owner | `representative` |
|
||||
| Store | permalink | `slug` | [1](https://letzshop.lu/en/dev)
|
||||
|
||||
---
|
||||
|
||||
## Order Management via API
|
||||
|
||||
Using the API, you can:
|
||||
|
||||
- Fetch unconfirmed orders
|
||||
- Confirm or reject them
|
||||
- Set tracking numbers
|
||||
- Handle returns
|
||||
|
||||
All of this requires at least "shop manager" API key access. Multi-store management is supported if rights allow. [1](https://letzshop.lu/en/dev)
|
||||
|
||||
### 1. Retrieve Unconfirmed Shipments
|
||||
|
||||
**Query:**
|
||||
```graphql
|
||||
query {
|
||||
shipments(state: unconfirmed) {
|
||||
nodes {
|
||||
id
|
||||
inventoryUnits {
|
||||
id
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
``` [1](https://letzshop.lu/en/dev)
|
||||
|
||||
---
|
||||
|
||||
### 2. Confirm/Reject Inventory Units
|
||||
|
||||
Use inventoryUnit IDs to confirm or reject:
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
confirmInventoryUnits(input: {
|
||||
inventoryUnits: [
|
||||
{ inventoryUnitId: "ID1", isAvailable: true },
|
||||
{ inventoryUnitId: "ID2", isAvailable: false },
|
||||
{ inventoryUnitId: "ID3", isAvailable: false }
|
||||
]
|
||||
}) {
|
||||
inventoryUnits {
|
||||
id
|
||||
state
|
||||
}
|
||||
errors {
|
||||
id
|
||||
code
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
``` [1](https://letzshop.lu/en/dev)
|
||||
|
||||
---
|
||||
|
||||
### 3. Handle Customer Returns
|
||||
|
||||
Use only after receiving returned items:
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
returnInventoryUnits(input: {
|
||||
inventoryUnits: [
|
||||
{ inventoryUnitId: "ID1" },
|
||||
{ inventoryUnitId: "ID2" }
|
||||
]
|
||||
}) {
|
||||
inventoryUnits {
|
||||
id
|
||||
state
|
||||
}
|
||||
errors {
|
||||
id
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
``` [1](https://letzshop.lu/en/dev)
|
||||
|
||||
---
|
||||
|
||||
### 4. Set Shipment Tracking Number
|
||||
|
||||
Include shipping provider and tracking code:
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
setShipmentTracking(input: {
|
||||
shipmentId: "SHIPMENT_ID",
|
||||
code: "TRACK123",
|
||||
provider: THE_SHIPPING_PROVIDER
|
||||
}) {
|
||||
shipment {
|
||||
tracking {
|
||||
code
|
||||
provider
|
||||
}
|
||||
}
|
||||
errors {
|
||||
code
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
``` [1](https://letzshop.lu/en/dev)
|
||||
|
||||
---
|
||||
|
||||
## Event System
|
||||
|
||||
Subscribe by contacting support@letzshop.lu. Events are delivered via an SNS-like message structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"Type": "Notification",
|
||||
"MessageId": "XXX",
|
||||
"TopicArn": "arn:aws:sns:eu-central-1:XXX:events",
|
||||
"Message": "{\"id\":\"XXX\",\"type\":\"XXX\",\"payload\":{...}}",
|
||||
"Timestamp": "2019-01-01T00:00:00.000Z",
|
||||
"SignatureVersion": "1",
|
||||
"Signature": "XXX",
|
||||
"SigningCertURL": "...",
|
||||
"UnsubscribeURL": "..."
|
||||
}
|
||||
``` [1](https://letzshop.lu/en/dev)
|
||||
|
||||
### Message Payload
|
||||
|
||||
Each event includes:
|
||||
|
||||
- `id`
|
||||
- `type` (e.g., ShipmentConfirmed, UserCreated…)
|
||||
- `payload` (object-specific data) [1](https://letzshop.lu/en/dev)
|
||||
|
||||
---
|
||||
|
||||
## Event Types & Payload Structure
|
||||
|
||||
A variety of event types are supported. Common ones include:
|
||||
|
||||
- `ShipmentConfirmed`, `ShipmentRefundCreated`
|
||||
- `UserAnonymized`, `UserCreated`, `UserDestroyed`, `UserUpdated`
|
||||
- `VariantWithPriceCrossedCreated`, `...Updated`
|
||||
- `StoreCategoryCreated`, `Destroyed`, `Updated`
|
||||
- `StoreCreated`, `Destroyed`, `Updated`
|
||||
|
||||
Exact payload structure varies per event type. [1](https://letzshop.lu/en/dev)
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This Markdown file captures all information from the Letzshop development page, formatted for use in your documentation or GitHub.
|
||||
This document has moved to the marketplace module docs: [API Reference](../modules/marketplace/api.md)
|
||||
|
||||
@@ -1,839 +1,3 @@
|
||||
# Letzshop Order Integration Guide
|
||||
|
||||
Complete guide for bidirectional order management with Letzshop marketplace via GraphQL API.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Architecture](#architecture)
|
||||
- [Setup and Configuration](#setup-and-configuration)
|
||||
- [Order Import](#order-import)
|
||||
- [Product Exceptions](#product-exceptions)
|
||||
- [Fulfillment Operations](#fulfillment-operations)
|
||||
- [Shipping and Tracking](#shipping-and-tracking)
|
||||
- [API Reference](#api-reference)
|
||||
- [Database Models](#database-models)
|
||||
- [Troubleshooting](#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
|
||||
|
||||
1. **Credentials Setup**: Store/Admin stores encrypted API key
|
||||
2. **Order Import**: System fetches unconfirmed shipments from Letzshop
|
||||
3. **Order Processing**: Orders stored locally with Letzshop IDs
|
||||
4. **Fulfillment**: Store confirms/rejects orders, sets tracking
|
||||
5. **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
|
||||
|
||||
1. Navigate to **Settings > Letzshop Integration**
|
||||
2. Enter your Letzshop API key
|
||||
3. Click **Test Connection** to verify
|
||||
4. Enable **Auto-Sync** if desired (optional)
|
||||
5. Click **Save**
|
||||
|
||||
#### Via Admin Portal
|
||||
|
||||
1. Navigate to **Marketplace > Letzshop**
|
||||
2. Select the store from the list
|
||||
3. Click **Configure Credentials**
|
||||
4. Enter the API key
|
||||
5. Click **Save & Test**
|
||||
|
||||
### Step 2: Test Connection
|
||||
|
||||
```bash
|
||||
# Test connection via API
|
||||
curl -X POST /api/v1/store/letzshop/test \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
1. Navigate to **Marketplace > Letzshop > Exceptions**
|
||||
2. Click **Resolve** on the pending exception
|
||||
3. Search for the correct product by name, SKU, or GTIN
|
||||
4. Select the product and click **Confirm**
|
||||
5. Optionally check "Apply to all exceptions with this GTIN" for bulk resolution
|
||||
|
||||
#### Via API
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
1. Collects GTINs of newly imported products
|
||||
2. Finds pending exceptions with matching GTINs
|
||||
3. 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:
|
||||
|
||||
```bash
|
||||
curl -X GET /api/v1/admin/order-exceptions/stats?store_id=1 \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"pending": 15,
|
||||
"resolved": 42,
|
||||
"ignored": 3,
|
||||
"total": 60,
|
||||
"orders_with_exceptions": 8
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see [Order Item Exception System](../implementation/order-item-exceptions.md).
|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Operations
|
||||
|
||||
### Confirm Order
|
||||
|
||||
Confirm that you can fulfill the order:
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
curl -X GET /api/v1/admin/orders/{order_id}/shipping-label \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
1. The shipment number (e.g., `H74683403433`) is captured during import
|
||||
2. The tracking URL can be constructed: `https://dispatchweb.fr/Tracky/Home/{shipment_number}`
|
||||
3. 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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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**:
|
||||
1. Verify API key in Letzshop merchant portal
|
||||
2. Regenerate API key if expired
|
||||
3. Check network connectivity
|
||||
4. 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**:
|
||||
1. Check Letzshop dashboard for unconfirmed orders
|
||||
2. Verify API key has order read permissions
|
||||
3. 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**:
|
||||
1. Check order state in Letzshop
|
||||
2. Verify inventory unit IDs are correct
|
||||
3. Check fulfillment queue for retry status
|
||||
4. Review error message in response
|
||||
|
||||
### Sync Logs
|
||||
|
||||
Check sync logs for detailed operation history:
|
||||
|
||||
```bash
|
||||
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**:
|
||||
1. Navigate to **Marketplace > Letzshop > Exceptions** tab
|
||||
2. Find the pending exceptions for this order
|
||||
3. Either:
|
||||
- **Resolve**: Assign the correct product from your catalog
|
||||
- **Ignore**: Mark as ignored if product will never be matched (still blocks confirmation)
|
||||
4. 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**:
|
||||
1. Re-sync the order to capture shipment data
|
||||
2. Check **Admin > Settings > Shipping** for carrier URL prefixes
|
||||
3. Verify the order has a valid `shipping_carrier` and `shipment_number`
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### For Stores
|
||||
|
||||
1. **Test connection** after setting up credentials
|
||||
2. **Import orders regularly** (or enable auto-sync)
|
||||
3. **Confirm orders promptly** to avoid delays
|
||||
4. **Set tracking** as soon as shipment is dispatched
|
||||
5. **Monitor sync logs** for any failures
|
||||
|
||||
### For Admins
|
||||
|
||||
1. **Review store status** regularly via admin dashboard
|
||||
2. **Assist stores** with connection issues
|
||||
3. **Monitor sync logs** for platform-wide issues
|
||||
4. **Set up alerts** for failed syncs (optional)
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Order Item Exception System](../implementation/order-item-exceptions.md)
|
||||
- [Marketplace Integration (CSV Import)](marketplace-integration.md)
|
||||
- [Store RBAC](../backend/store-rbac.md)
|
||||
- [Admin Integration Guide](../backend/admin-integration-guide.md)
|
||||
- [Exception Handling](../development/exception-handling.md)
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
- **v1.2** (2025-12-20): Shipping & Tracking enhancements
|
||||
- Added `shipment_number`, `shipping_carrier`, `tracking_url` fields 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
|
||||
|
||||
- **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
|
||||
This document has moved to the marketplace module docs: [Order Integration](../modules/marketplace/order-integration.md)
|
||||
|
||||
Binary file not shown.
@@ -1,182 +1 @@
|
||||
# Media Library
|
||||
|
||||
The media library provides centralized management of uploaded files (images, documents) for stores. Each store has their own isolated media storage.
|
||||
|
||||
## Overview
|
||||
|
||||
- **Storage Location**: `uploads/stores/{store_id}/{folder}/`
|
||||
- **Supported Types**: Images (JPG, PNG, GIF, WebP), Documents (PDF)
|
||||
- **Max File Size**: 10MB per file
|
||||
- **Automatic Thumbnails**: Generated for images (200x200px)
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Admin Media Management
|
||||
|
||||
Admins can manage media for any store:
|
||||
|
||||
```
|
||||
GET /api/v1/admin/media/stores/{store_id} # List store's media
|
||||
POST /api/v1/admin/media/stores/{store_id}/upload # Upload file
|
||||
GET /api/v1/admin/media/stores/{store_id}/{id} # Get media details
|
||||
DELETE /api/v1/admin/media/stores/{store_id}/{id} # Delete media
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `skip` | int | Pagination offset (default: 0) |
|
||||
| `limit` | int | Items per page (default: 100, max: 1000) |
|
||||
| `media_type` | string | Filter by type: `image`, `video`, `document` |
|
||||
| `folder` | string | Filter by folder: `products`, `general`, etc. |
|
||||
| `search` | string | Search by filename |
|
||||
|
||||
### Upload Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "File uploaded successfully",
|
||||
"media": {
|
||||
"id": 1,
|
||||
"filename": "product-image.jpg",
|
||||
"file_url": "/uploads/stores/1/products/abc123.jpg",
|
||||
"url": "/uploads/stores/1/products/abc123.jpg",
|
||||
"thumbnail_url": "/uploads/stores/1/thumbnails/thumb_abc123.jpg",
|
||||
"media_type": "image",
|
||||
"file_size": 245760,
|
||||
"width": 1200,
|
||||
"height": 800
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Media Picker Component
|
||||
|
||||
A reusable Alpine.js component for selecting images from the media library.
|
||||
|
||||
### Usage in Templates
|
||||
|
||||
```jinja2
|
||||
{% from 'shared/macros/modals.html' import media_picker_modal %}
|
||||
|
||||
{# Single image selection #}
|
||||
{{ media_picker_modal(
|
||||
id='media-picker-main',
|
||||
show_var='showMediaPicker',
|
||||
store_id_var='storeId',
|
||||
title='Select Image'
|
||||
) }}
|
||||
|
||||
{# Multiple image selection #}
|
||||
{{ media_picker_modal(
|
||||
id='media-picker-additional',
|
||||
show_var='showMediaPickerAdditional',
|
||||
store_id_var='storeId',
|
||||
multi_select=true,
|
||||
title='Select Additional Images'
|
||||
) }}
|
||||
```
|
||||
|
||||
### JavaScript Integration
|
||||
|
||||
Include the media picker mixin in your Alpine.js component:
|
||||
|
||||
```javascript
|
||||
function myComponent() {
|
||||
return {
|
||||
...data(),
|
||||
|
||||
// Include media picker functionality
|
||||
...mediaPickerMixin(() => this.storeId, false),
|
||||
|
||||
storeId: null,
|
||||
|
||||
// Override to handle selected image
|
||||
setMainImage(media) {
|
||||
this.form.image_url = media.url;
|
||||
},
|
||||
|
||||
// Override for multiple images
|
||||
addAdditionalImages(mediaList) {
|
||||
const urls = mediaList.map(m => m.url);
|
||||
this.form.additional_images.push(...urls);
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Media Picker Mixin API
|
||||
|
||||
| Property/Method | Description |
|
||||
|-----------------|-------------|
|
||||
| `showMediaPicker` | Boolean to show/hide main image picker modal |
|
||||
| `showMediaPickerAdditional` | Boolean to show/hide additional images picker |
|
||||
| `mediaPickerState` | Object containing loading, media array, selected items |
|
||||
| `openMediaPickerMain()` | Open picker for main image |
|
||||
| `openMediaPickerAdditional()` | Open picker for additional images |
|
||||
| `loadMediaLibrary()` | Fetch media from API |
|
||||
| `uploadMediaFile(event)` | Handle file upload |
|
||||
| `toggleMediaSelection(media)` | Select/deselect a media item |
|
||||
| `confirmMediaSelection()` | Confirm selection and call callbacks |
|
||||
| `setMainImage(media)` | Override to handle main image selection |
|
||||
| `addAdditionalImages(mediaList)` | Override to handle multiple selections |
|
||||
|
||||
## File Storage
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
uploads/
|
||||
└── stores/
|
||||
└── {store_id}/
|
||||
├── products/ # Product images
|
||||
├── general/ # General uploads
|
||||
└── thumbnails/ # Auto-generated thumbnails
|
||||
```
|
||||
|
||||
### URL Paths
|
||||
|
||||
Files are served from `/uploads/` path:
|
||||
- Full image: `/uploads/stores/1/products/image.jpg`
|
||||
- Thumbnail: `/uploads/stores/1/thumbnails/thumb_image.jpg`
|
||||
|
||||
## Database Model
|
||||
|
||||
```python
|
||||
class MediaFile(Base):
|
||||
id: int
|
||||
store_id: int
|
||||
filename: str # Stored filename (UUID-based)
|
||||
original_filename: str # Original upload name
|
||||
file_path: str # Relative path from uploads/
|
||||
thumbnail_path: str # Thumbnail relative path
|
||||
media_type: str # image, video, document
|
||||
mime_type: str # image/jpeg, etc.
|
||||
file_size: int # Bytes
|
||||
width: int # Image width
|
||||
height: int # Image height
|
||||
folder: str # products, general, etc.
|
||||
```
|
||||
|
||||
## Product Images
|
||||
|
||||
Products support both a main image and additional images:
|
||||
|
||||
```python
|
||||
class Product(Base):
|
||||
primary_image_url: str # Main product image
|
||||
additional_images: list[str] # Array of additional image URLs
|
||||
```
|
||||
|
||||
### In Product Forms
|
||||
|
||||
The product create/edit forms include:
|
||||
1. **Main Image**: Single image with preview and media picker
|
||||
2. **Additional Images**: Grid of images with add/remove functionality
|
||||
|
||||
Both support:
|
||||
- Browsing the store's media library
|
||||
- Uploading new images directly
|
||||
- Entering external URLs manually
|
||||
This document has moved to the CMS module docs: [Media Library](../modules/cms/media-library.md)
|
||||
|
||||
@@ -1,135 +1,3 @@
|
||||
# Subscription Tier Management
|
||||
|
||||
This guide explains how to manage subscription tiers and assign features to them in the admin panel.
|
||||
|
||||
## Accessing Tier Management
|
||||
|
||||
Navigate to **Admin → Billing & Subscriptions → Subscription Tiers** or go directly to `/admin/subscription-tiers`.
|
||||
|
||||
## Dashboard Overview
|
||||
|
||||
The tier management page displays:
|
||||
|
||||
### Stats Cards
|
||||
- **Total Tiers**: Number of configured subscription tiers
|
||||
- **Active Tiers**: Tiers currently available for subscription
|
||||
- **Public Tiers**: Tiers visible to stores (excludes enterprise/custom)
|
||||
- **Est. MRR**: Estimated Monthly Recurring Revenue
|
||||
|
||||
### Tier Table
|
||||
|
||||
Each tier shows:
|
||||
|
||||
| Column | Description |
|
||||
|--------|-------------|
|
||||
| # | Display order (affects pricing page order) |
|
||||
| Code | Unique identifier (e.g., `essential`, `professional`) |
|
||||
| Name | Display name shown to stores |
|
||||
| Monthly | Monthly price in EUR |
|
||||
| Annual | Annual price in EUR (or `-` if not set) |
|
||||
| Orders/Mo | Monthly order limit (or `Unlimited`) |
|
||||
| Products | Product limit (or `Unlimited`) |
|
||||
| Team | Team member limit (or `Unlimited`) |
|
||||
| Features | Number of features assigned |
|
||||
| Status | Active, Private, or Inactive |
|
||||
| Actions | Edit Features, Edit, Activate/Deactivate |
|
||||
|
||||
## Managing Tiers
|
||||
|
||||
### Creating a New Tier
|
||||
|
||||
1. Click **Create Tier** button
|
||||
2. Fill in the tier details:
|
||||
- **Code**: Unique lowercase identifier (cannot be changed after creation)
|
||||
- **Name**: Display name for the tier
|
||||
- **Monthly Price**: Price in cents (e.g., 4900 for €49.00)
|
||||
- **Annual Price**: Optional annual price in cents
|
||||
- **Order Limit**: Leave empty for unlimited
|
||||
- **Product Limit**: Leave empty for unlimited
|
||||
- **Team Members**: Leave empty for unlimited
|
||||
- **Display Order**: Controls sort order on pricing pages
|
||||
- **Active**: Whether tier is available
|
||||
- **Public**: Whether tier is visible to stores
|
||||
3. Click **Create**
|
||||
|
||||
### Editing a Tier
|
||||
|
||||
1. Click the **pencil icon** on the tier row
|
||||
2. Modify the tier properties
|
||||
3. Click **Update**
|
||||
|
||||
Note: The tier code cannot be changed after creation.
|
||||
|
||||
### Activating/Deactivating Tiers
|
||||
|
||||
- Click the **check-circle icon** to activate an inactive tier
|
||||
- Click the **x-circle icon** to deactivate an active tier
|
||||
|
||||
Deactivating a tier:
|
||||
- Does not affect existing subscriptions
|
||||
- Hides the tier from new subscription selection
|
||||
- Can be reactivated at any time
|
||||
|
||||
## Managing Features
|
||||
|
||||
### Assigning Features to a Tier
|
||||
|
||||
1. Click the **puzzle-piece icon** on the tier row
|
||||
2. A slide-over panel opens showing all available features
|
||||
3. Features are grouped by category:
|
||||
- Analytics
|
||||
- Product Management
|
||||
- Order Management
|
||||
- Marketing
|
||||
- Support
|
||||
- Integration
|
||||
- Branding
|
||||
- Team
|
||||
|
||||
4. Check/uncheck features to include in the tier
|
||||
5. Use **Select all** or **Deselect all** per category for bulk actions
|
||||
6. The footer shows the total number of selected features
|
||||
7. Click **Save Features** to apply changes
|
||||
|
||||
### Feature Categories
|
||||
|
||||
| Category | Example Features |
|
||||
|----------|------------------|
|
||||
| Analytics | Basic Analytics, Analytics Dashboard, Custom Reports |
|
||||
| Product Management | Bulk Edit, Variants, Bundles, Inventory Alerts |
|
||||
| Order Management | Order Automation, Advanced Fulfillment, Multi-Warehouse |
|
||||
| Marketing | Discount Codes, Abandoned Cart, Email Marketing, Loyalty |
|
||||
| Support | Email Support, Priority Support, Phone Support, Dedicated Manager |
|
||||
| Integration | Basic API, Advanced API, Webhooks, Custom Integrations |
|
||||
| Branding | Theme Customization, Custom Domain, White Label |
|
||||
| Team | Team Management, Role Permissions, Audit Logs |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Tier Pricing Strategy
|
||||
|
||||
1. **Essential**: Entry-level with basic features and limits
|
||||
2. **Professional**: Mid-tier with increased limits and key integrations
|
||||
3. **Business**: Full-featured for growing businesses
|
||||
4. **Enterprise**: Custom pricing with unlimited everything
|
||||
|
||||
### Feature Assignment Tips
|
||||
|
||||
- Start with fewer features in lower tiers
|
||||
- Ensure each upgrade tier adds meaningful value
|
||||
- Keep support features as upgrade incentives
|
||||
- API access typically belongs in Business+ tiers
|
||||
|
||||
### Stripe Integration
|
||||
|
||||
For each tier, you can optionally configure:
|
||||
- **Stripe Product ID**: Link to Stripe product
|
||||
- **Stripe Monthly Price ID**: Link to monthly price
|
||||
- **Stripe Annual Price ID**: Link to annual price
|
||||
|
||||
These are required for automated billing via Stripe Checkout.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Subscription & Billing System](../features/subscription-billing.md) - Complete billing documentation
|
||||
- [Feature Gating System](../implementation/feature-gating-system.md) - Technical feature gating details
|
||||
This document has moved to the billing module docs: [Tier Management](../modules/billing/tier-management.md)
|
||||
|
||||
Reference in New Issue
Block a user