TeleBot Configuration:
- Added TeleBot API URL and API key to docker-compose.yml
- Configured to connect to telebot-service:5000 internally
- Enables customer notifications via Telegram bot
Expired Payment Handling:
- Auto-cancel orders when payment status is Expired
- Only cancels orders in PendingPayment status
- Logs cancellation for audit trail
Customer View Improvements:
- Hide cancelled orders from customer order lists
- Filters applied to both GetOrdersByIdentityAsync and GetOrdersByCustomerIdAsync
- Prevents confusion from displaying cancelled/expired orders
This resolves:
- No notifications to customers (TeleBot not configured)
- No notifications to admin (TeleBot connection failed)
- Expired orders remaining visible to customers
- Orders not auto-cancelled when payment expires
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Webhook Improvements:
- Added Confirmations field to PaymentWebhookDto (default: 0)
- Updated webhook controller to pass confirmations to service layer
- Fixed notification logic to match order update conditions
Payment Confirmation Logic:
- Paid (2): Confirmed immediately regardless of confirmations
- Overpaid (3): Confirmed immediately regardless of confirmations
- Completed (7): Requires 3+ blockchain confirmations
- Notifications only sent when order is actually updated
This prevents premature notifications for unconfirmed 'Completed' status
while maintaining immediate processing for 'Paid' and 'Overpaid' statuses.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added quick action button on Packing tab to dispatch orders
- Created dispatch modal with tracking number input
- Modal includes tracking number, estimated days, and notes fields
- Button appears on both desktop table and mobile card views
- Fixes workflow gap where Packing orders had no quick action
- Orders now properly flow: Accepted → Packing → Dispatched
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added PaymentStatus.Overpaid to notification trigger conditions
- Overpaid payments now update order status to PaymentReceived
- Overpaid payments now send admin push notifications
- Overpaid payments now send TeleBot customer notifications
- Resolves issue where successful overpayments were silent
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed Issues:
1. Removed "maskable" purpose from icons (was causing padding warnings)
2. Added screenshots with form_factor for richer install UI
- Desktop: form_factor: "wide"
- Mobile: form_factor: "narrow"
Changes:
- Icons now use "purpose: any" only (no maskable)
- Added screenshots array with wide/narrow form factors
- This enables richer PWA install prompts on supported browsers
All PWA manifest warnings should now be resolved.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problems Fixed:
1. Blank white screen on initial load (loading screen had display:none)
2. Only showed once (sessionStorage.blazorLoaded prevented repeat shows)
3. Fast connections meant users never saw it
Solution:
1. Removed display:none from HTML - screen visible immediately
2. Removed sessionStorage check - shows on every page load
3. Screen visible by default, hides when Blazor.start() completes
Behavior Now:
- Loading screen appears instantly (no blank white screen)
- Shows on every page load (full page refresh)
- Hides when SignalR connection established
- Works correctly with slow/throttled connections
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Loading screen was hiding immediately without waiting for Blazor
- Page detection logic was too restrictive (only /blazor paths)
- Most admin pages don't have Blazor components, so screen hid instantly
Solution:
- Blazor Server is loaded on ALL admin pages via _Layout.cshtml
- Removed restrictive path checking (was checking for /blazor or components)
- Now always calls Blazor.start() and waits for SignalR connection
- Loading screen properly shows while SignalR establishes connection
Expected behavior:
- First load: Screen shows → Blazor connects → Screen fades out
- Console: "Starting Blazor Server..." → "Started successfully" → "Hiding"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**:
- GetAvailableCurrenciesAsync() was routing internal API calls through Tor
- Caused SOCKS connection failures: "SOCKS server failed to connect"
- Internal Docker network calls don't need Tor privacy layer
**Root Cause**:
- Line 617-618 used OR logic: LittleShop:UseTor OR Privacy:EnableTor
- Production config: LittleShop__UseTor=false, Privacy__EnableTor=true
- OR condition meant Tor was enabled for all API calls
**Solution**:
- Only check LittleShop:UseTor (explicitly for API calls)
- Privacy:EnableTor now only affects external Telegram API calls
- Internal calls (http://littleshop:5000) bypass Tor completely
**Benefits**:
- No more SOCKS connection errors
- Faster internal API responses (no Tor overhead)
- Tor still protects external Telegram communications
**File**: TeleBot/TeleBot/Services/LittleShopService.cs:619
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Ephemeral sessions weren't persisted after checkout
- Sessions ephemeral by default, only persisted during CheckoutFlow
- After checkout completes, state changes to MainMenu
- SavedAddress was lost because session wasn't saved
Fix implemented:
- Persist ephemeral sessions when SavedAddress is not null
- Also persist when cart is non-empty (preserve cart state)
- User's saved shipping address now available on next checkout
- Improves UX - no need to re-enter address every time
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>