Root cause: Orders created with CustomerInfo had NULL IdentityReference
- CancelOrderAsync checked order.IdentityReference != identityReference
- NULL != "telegram:12345:username" → always returned false
- User saw "already processed" error even for pending orders
Fix implemented:
- Include Customer entity in CancelOrderAsync query
- Extract Telegram user ID from identity reference format
- Match against Customer.TelegramUserId for modern orders
- Fallback to IdentityReference matching for legacy orders
- Enhanced logging to debug ownership/status issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added delete order functionality with identity verification
- OrderDetailsMenu now shows conditional buttons based on order/payment state
- Retry payment if no payments exist
- View payment details if payment pending
- Delete order option for all pending orders
- Full client SDK implementation for CancelOrderAsync
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: When clicking Buy button, product details message appeared at bottom
but Telegram didn't auto-scroll to show it, leaving users confused.
Solution: Use replyToMessageId parameter to thread product details as a reply
to the user's button click message. This:
- Creates visual thread connection
- Encourages Telegram to show the response prominently
- Makes conversation flow more natural
Also wrapped activity tracking in try-catch to prevent blocking.
Technical Changes:
- ProductCarouselService: Added replyToMessageId optional parameter
- SendPhotoAsync/SendTextMessageAsync: Use replyToMessageId with allowSendingWithoutReply
- HandleProductDetail: Pass message.MessageId to carousel service
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Error: MenuBuilder.PaymentCurrencyMenu does not exist
Fix: Changed to MenuBuilder.PaymentMethodMenu (correct method name)
TeleBot now compiles successfully.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: Activity tracker DNS errors (littleshop-admin:8080) were blocking
checkout flow, preventing shipping address prompt from showing.
Solution: Wrap activity tracking in try-catch with warning log.
Checkout flow continues even if activity tracking fails.
This ensures users can complete checkout even if analytics are unavailable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1. Remove /help from main menu
- Removed "❓ Help" button from main menu
- Help still available via /help command
2. Fix support refresh clearing messages
- Changed from editing message to sending new message
- Preserves message history when refreshing
- Shows "No new messages" if no updates instead of clearing
- Better error handling with user-friendly alerts
3. Add retry payment button for failed payments
- New "🔄 Retry Payment" button when payment creation fails
- Shows order ID (friendly reference) in error message
- PaymentFailedMenu: Retry, View Basket, Main Menu options
- HandleRetryPayment: Re-shows currency selection
- Preserves order and allows payment retry without recreating order
Technical Changes:
- MenuBuilder: Added PaymentFailedMenu method
- CallbackHandler: Added retry_payment case and HandleRetryPayment method
- HandlePayment: Updated error messages to use PaymentFailedMenu
- HandleRefreshConversation: Sends new message instead of editing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1. Basket Summary Below Navigation
- Welcome message now shows basket items and total
- Format: "🛒 Basket: X item(s) - £X.XX"
- Only shown when basket has items
2. Order Display Improvements
- Order GUID replaced with friendly reference (last 8 chars: #XXXXXXXX)
- Added "View Payment Details" button for pending payment orders
- Button shows QR code, amount, address, and time until expiry
- Variants now properly displayed in order items (already working via commit 330116e)
3. Payment Details View (New Feature)
- HandleViewPayment: Shows payment info with QR code
- Displays: Currency, amount, address, expiry time
- Shows time remaining until payment expires
- QR code generation for easy mobile payment
- OrderDetailsMenu: Context-aware navigation buttons
4. Postal Address Auto-Load (Verified Working)
- Checkout automatically detects saved addresses
- Offers to use saved address with preview
- One-click selection or option to enter new address
- SavedAddress copied to OrderFlow when selected
Technical Changes:
- MessageFormatter.FormatWelcome: Added optional cart parameter
- MessageFormatter.FormatOrder: Uses friendly order reference
- MenuBuilder.OrderDetailsMenu: New menu with payment button
- CallbackHandler.HandleViewPayment: Payment details with QR
- CallbackHandler.HandleMainMenu: Pass cart to welcome formatter
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1. Show variants immediately on product page (removed duplicate buy button)
- Variants now display directly on product details page
- No intermediate "select variant" button needed
- Faster checkout flow
2. Add post-purchase prompt (Checkout or Continue Shopping)
- After adding to basket, shows "What would you like to do next?"
- Options: View Basket, Checkout, or Continue Shopping
- Continue Shopping returns to the product page
3. Remove quantity +/- buttons from product details
- Simplified to single item purchase only
- Cleaner interface for mobile users
4. Rename "Cart" to "Basket" throughout
- All user-facing text updated
- "Add to Cart" → "Add to Basket"
- "Shopping Cart" → "Shopping Basket"
- "View Cart" → "View Basket"
- "Clear Cart" → "Clear Basket"
Technical Changes:
- MenuBuilder.ProductDetailMenu: Now shows variant selection inline
- CallbackHandler: Updated to use ProductDetailMenu for variant updates
- Added PostAddToCartMenu for post-purchase options
- HandleAddToCart/HandleConfirmVariant: Show prompt instead of returning to product
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Selecting Size then Color would reset Size selection
- Users could never get "Add to Cart" button with multiple variant types
- Each selection created a NEW list, wiping previous choices
Root Cause:
- HandleSetVariant created: new List<string> { variantName }
- This replaced all previous selections instead of accumulating
Fix:
1. Get existing selected variants from session
2. Find the variant type of newly selected variant
3. Remove any previous selection from the SAME type (allow changing choice)
4. Add the new selection
5. Save accumulated list back to session
Example behavior now:
- Select "Red" (Color) → selectedVariants = ["Red"]
- Select "Large" (Size) → selectedVariants = ["Red", "Large"] ✅
- Select "Blue" (Color) → selectedVariants = ["Blue", "Large"] ✅
- Can now confirm when all types selected
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Product with Size + Color only required selecting 1 variant total
- User could add to cart after selecting just Size OR Color, not both
Root Cause:
- Logic checked if selectedVariants.Count == 1 for single items
- Didn't verify that all variant types were covered
Fix:
- For single items: Check that each variant type has at least one selection
- Logic: variantGroups.All(g => g.Any(v => selectedVariants.Contains(v.Name)))
- For multi-buy: Keep existing logic (total count == quantity)
Now users must select:
- Size + Color products: Must pick both Size AND Color
- Size only products: Must pick Size only
- Etc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Order queries were missing .ThenInclude(oi => oi.ProductVariant)
which caused ProductVariantName to be null in order DTOs even though
ProductVariantId was stored correctly.
Fixed queries:
- GetAllOrdersAsync (admin panel order list)
- GetOrdersByIdentityAsync (TeleBot order lookup)
- GetOrdersByCustomerIdAsync (customer order history)
- UpdateOrderStatusAsync (order status updates)
Now both TeleBot and admin panel will show which product variation
was selected in order details and order lists.
- OrderListMenu now shows item summary (e.g., '2x Product Name' or 'Product Name +2 more')
- FormatCart enhanced to show variant selections and multi-buy indicators
- FormatOrder enhanced to show variant names and detailed item breakdown
- Cart now clearly distinguishes between multi-buy bundles and regular items
- Order confirmations now show full product and variant details
Replaced generic GUID displays with human-readable product information for better UX.
- Changed HandleMainMenu to send new messages instead of editing
- Changed HandleBrowse to send new messages for category navigation
- Changed HandleViewCart to use SendNewCartMessage (already existed)
- Changed HandleViewOrders to send new messages for order list
- Changed HandleViewOrder to send new messages for order details
This ensures the active conversation always appears at the bottom of the chat
instead of mid-thread when users click menu buttons. Provides better UX and
natural conversation flow in Telegram.
- Added SavedShippingAddress model to store user addresses
- Added Privacy.SaveShippingAddress preference (null = not asked, true/false = user choice)
- Enhanced checkout flow to offer using saved address if available
- Ask once about saving address, respect user's choice going forward
- Automatically save/not save based on user preference in future orders
- Added menu options: Use Saved Address, Enter New Address, Save Address (Yes/No)
- Enhanced CallbackHandler with save address handlers
- Updated MessageHandler to prompt for save preference on first use
User will only be asked once about saving addresses. Account reset clears preference.
- Removed informational 'Variants: Size: X options, Color: Y options' button
- Renamed 'Add to Cart' button to 'BUY {product.Name}' for clearer UX
- Applied to both multi-buy single item and regular quantity purchase buttons
**Root Cause**: EF Core Include() was not properly materializing the Variants navigation
property despite correct SQL JOIN generation.
**Solution**: Load variants separately and manually group by ProductId for DTO mapping.
This bypasses EF Core's navigation property fixup issues.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
AsNoTracking() prevents EF Core from properly wiring up navigation properties.
Removing it allows Include() to populate Variants collection correctly.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**: EF Core was not materializing Variants navigation property when using
.Select() projection directly in the query. The .Include() was being ignored.
**Solution**: Changed approach to:
1. Load entities with .Include() + .ToListAsync() first
2. Then project to DTO with in-memory .Select()
This ensures navigation properties are fully loaded before mapping to DTOs.
**Impact**: Variants will now properly appear in all product API responses.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Previous commit (e931f77) only fixed GetAllProductsAsync and GetProductByIdAsync.
This commit fixes GetProductsByCategoryAsync which also had the broken filtered Include syntax.
**Impact**: Variants will now appear when browsing products by category in TeleBot.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Critical bug where ProductVariants were never loaded from database.
**Problem:**
`.Include(p => p.Variants.Where(v => v.IsActive))` syntax was NOT
generating SQL JOIN statements in EF Core 9.0, causing all products
to return empty variants array even when variants exist in database.
**Solution:**
- Changed to simple `.Include(p => p.Variants)`
- Filtering still happens in DTO mapping (Select statement)
- Only IsActive variants are returned to API consumers
**Impact:**
- TeleBot can now display product variants with selection UI
- Variant pricing and stock levels now visible to customers
- Multi-variant products (e.g., Size/Color) now functional
**Test Case:**
Product 131cc3ad-07f4-4ec9-89ca-b05a0b4cfb41 has 7 variants:
- Size: Small, Medium, Large, XL
- Color: Black, White, Navy Blue
These will now appear in API responses and TeleBot UI.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Critical fixes to ensure smooth deployments and prevent future outages:
**docker-compose.yml:**
- Fixed image name: littleshop:latest → localhost:5000/littleshop:latest
- Fixed subnet: 172.21.0.0/16 → 172.23.0.0/16 (matches production)
- Fixed environment: Production → Development (matches current production)
**.gitlab-ci.yml:**
- Fixed TeleBot API URL: http://littleshop-admin:8080 → http://littleshop:5000
- Removed duplicate network flag (was causing issues)
- Added explicit network connection command for littleshop_littleshop-network
- Ensures TeleBot can communicate with LittleShop API on deployment
**CLAUDE.md:**
- Documented October 4, 2025 incident and recovery
- Added comprehensive deployment best practices
- Documented pre-deployment checklist
- Added manual deployment commands for emergencies
- Documented network architecture and container configuration
**Root Cause of Previous Failure:**
TeleBot was trying to connect to non-existent hostname "littleshop-admin"
on wrong network, causing authentication failures and data unavailability.
**Verification:**
All changes tested in production and confirmed working. TeleBot now
successfully authenticates and communicates with LittleShop API.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Prevents future deployment failures by automatically applying database
schema migrations during deployment process.
Changes:
- Added migration step that runs AFTER stopping containers
- Automatically detects .sql files in LittleShop/Migrations/
- Creates timestamped backup before applying each migration
- Applies migrations using sqlite3 in Alpine container
- Properly handles volume mounting for littleshop_littleshop_data
This prevents issues like the October 4 incident where ProductVariant
schema changes were deployed without updating the database, causing
complete system outage.
Migration workflow:
1. Stop all containers
2. Check for migration files
3. Create database backup
4. Apply migrations
5. Start containers with updated schema
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Critical fix for production deployment issue where code changes were
deployed without corresponding database schema updates.
Changes:
- Add Price column to ProductVariants table (decimal 18,2, nullable)
- Add ProductVariantId column to OrderItems table (TEXT, nullable)
- Add index on OrderItems.ProductVariantId for query performance
This migration was manually applied to production on 2025-10-04 to
resolve "no such column: p2.Price" errors that broke the product
catalog API.
Future deployments must include database migration steps in CI/CD.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Added Price override input field to the JavaScript variant collection editor on the product Edit page.
**Changes:**
- Added Price input field (with £ symbol) in variant details section
- Updated serialization to save Price to VariantsJson
- Excluded Price from variant label generation
- Updated button text: "Price, Stock & Weight Details"
**Location:**
Product Edit > Variants Collection > Toggle Details > Price Override
Now variant prices can be set through BOTH methods:
1. Individual variant management (CreateVariant/EditVariant)
2. Bulk variant collection editor (product Edit page)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enables individual variants to have their own prices, overriding the base product price.
**Database Changes:**
- Added Price (decimal?, nullable) to ProductVariants table
- Added ProductVariantId to OrderItems table with foreign key relationship
- Created index on OrderItems.ProductVariantId for performance
**API Changes:**
- ProductVariantDto: Added Price field
- CreateProductVariantDto: Added Price field with validation
- UpdateProductVariantDto: Added Price field
- OrderItemDto: Added ProductVariantId and ProductVariantName
- CreateOrderItemDto: Added ProductVariantId
**Business Logic:**
- OrderService: Variant price overrides base price (but multi-buy takes precedence)
- ProductService: All variant CRUD operations support Price field
**Admin UI:**
- CreateVariant: Price input with £ symbol and base price placeholder
- EditVariant: Price editing with £ symbol
- ProductVariants list: Shows variant price or "(base)" indicator
**Client Library:**
- Updated all DTOs to match server-side changes
- Full support for variant pricing in order creation
**Migration:**
- EF Core migration: 20251003173458_AddVariantPricing
- Backward compatible: NULL values supported for existing data
**Use Case:**
Products with size/color variants can now have different prices:
- Small T-shirt: £15.00 (variant override)
- Medium T-shirt: £18.00 (uses base price)
- Large T-shirt: £20.00 (variant override)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Issue
Order creation failing with 400 BadRequest when using CustomerInfo (Telegram users).
Validator required IdentityReference to always be populated, but it's null when using CustomerInfo.
## Root Cause
CreateOrderDtoValidator.cs:10-12 enforced NotEmpty() on IdentityReference unconditionally.
TeleBot sends CustomerInfo for identified users, leaving IdentityReference null.
## Solution
Updated validator to accept EITHER IdentityReference OR CustomerInfo:
- New rule: At least one must be provided
- IdentityReference validation only applies when it's provided (.When() condition)
- Maintains backward compatibility with anonymous orders
## Impact
✅ Telegram bot orders can now be created successfully
✅ Anonymous orders still require IdentityReference
✅ Proper validation error messages for both scenarios
## Testing Required
- Create order via Telegram bot (with CustomerInfo)
- Create anonymous order (with IdentityReference)
- Verify both scenarios work correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Issue
Previous fix enabled LittleShop__UseTor=true, which tried to route internal
Docker API calls through Tor. Tor correctly rejected these private addresses.
## Root Cause
Two separate Tor configuration flags exist:
1. Privacy__EnableTor - Controls Telegram Bot API calls (external, public)
2. LittleShop__UseTor - Controls LittleShop API calls (internal, private)
## Solution
- Set LittleShop__UseTor=false (internal calls direct - no Tor)
- Set Privacy__EnableTor=true (Telegram API calls via Tor)
## Impact
✅ Telegram Bot API calls now route through Tor (privacy protected)
✅ Internal API calls go direct (no Tor rejection errors)
✅ Proper separation of concerns
## Technical Details
TelegramBotService.cs:77 checks Privacy:EnableTor
Program.cs:63 checks LittleShop:UseTor
These control different HTTP clients for different purposes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Issue
TeleBot was bypassing Tor gateway despite infrastructure being available.
## Root Cause
Deployment configuration explicitly disabled Tor:
- LittleShop__UseTor=false (line 118)
## Fix
Changed deployment configuration to enable Tor routing:
- LittleShop__UseTor=true
## Impact
✅ All Telegram API calls now route through Tor network
✅ Bot's real IP hidden from Telegram servers
✅ Enhanced privacy protection
⚠️ Slight latency increase due to Tor overhead
## Verification Required
Monitor tor-gateway logs after deployment to confirm traffic routing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Critical Bug Fixes
### Currency Display (£ vs $)
- Fix MenuBuilder.cs: Replace $ with £ for product prices (line 60) and order totals (line 329)
- Fix ProductCarouselService.cs: Replace $ with £ in product captions and multi-buy offers (lines 317, 325)
- Fix CallbackHandler.cs: Replace $ with £ in order confirmation message (line 800)
### Payment Amount Display Bug
- Fix MessageFormatter.cs: Remove flawed crypto detection logic (< 1.0m check)
- Bug: Order for £700 in ETH displayed as "£1.66" instead of "1.66 ETH"
- Root cause: RequiredAmount is always stored as crypto amount, not fiat
- Solution: Always display RequiredAmount with crypto symbol
- Impact: Fixes display for XMR, DOGE, LTC, and large ETH amounts
## Security: Remove PGP Encryption Feature
### Critical Security Issue Resolved
- PGP "encryption" was only Base64 encoding - NOT real encryption
- Shipping addresses stored as easily decoded text
- False sense of security for users
### Changes Made
- Mark EncryptWithPGP method as [Obsolete] in PrivacyService.cs
- Remove PGP encryption logic from order creation (LittleShopService.cs)
- Mark PGP properties as [Obsolete] in UserSession.cs models
- Disable EnablePGPEncryption feature flag in appsettings.json
- Add comments explaining feature removal
### Recommendation
Implement proper PGP encryption using BouncyCastle in future, or keep removed.
## Testing Required
- Verify all prices display with £ symbol
- Verify crypto payments show correct amount format (e.g., "1.66000000 ETH")
- Verify no PGP options appear in UI
- Test order creation without PGP encryption
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>