- Add Gitea Actions workflow for manual AlexHost deployment
- Add docker-compose.alexhost.yml for production deployment
- Add deploy-alexhost.sh script with server-side build support
- Add Bot Control feature (Start/Stop/Restart) for remote bot management
- Add discovery control endpoint in TeleBot
- Update TeleBot with StartPollingAsync/StopPolling/RestartPollingAsync
- Fix platform architecture issues by building on target server
- Update docker-compose configurations for all environments
Deployment tested successfully:
- TeleShop: healthy at https://teleshop.silentmary.mywire.org
- TeleBot: healthy with discovery integration
- SilverPay: connectivity verified
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add LittleShopSessionId and MessageCount properties to UserSession model
- Integrate SessionManager with BotManagerService for remote session tracking
- Wire up SessionManager.SetBotManagerService() at startup in Program.cs
- Create remote sessions via BotManagerService.StartSessionAsync() when users connect
- Update remote sessions periodically (every 10 messages) via UpdateSessionAsync()
- Fix live activity feed to show newest records at top by reversing array iteration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When TeleBot starts without a token configured, the TelegramBotService
returns early from StartAsync without creating a bot client. Previously,
UpdateBotTokenAsync only worked when _botClient was already initialized.
This fix changes the condition to also start the bot if _botClient is
null, enabling remote configuration via the discovery API to properly
start Telegram polling.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The Telegram API returns JSON with snake_case properties (first_name, is_bot)
but the DTOs use PascalCase. Added JsonSerializerOptions with
PropertyNameCaseInsensitive and SnakeCaseLower naming policy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fixed BotService to prevent duplicate bot registrations by checking for existing bot with same name/type
- Updated existing bot record instead of creating duplicates on re-registration
- Configured SilverPay integration with production API key
- Updated TeleBot configuration for local development (localhost API URL, Tor disabled)
This ensures single bot instances and proper payment gateway integration for testing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Changed JSON naming policy from CamelCase to SnakeCaseLower for SilverPay API compatibility
- Fixed field name from 'fiat_amount' to 'amount' in request body
- Used unique payment ID instead of order ID to avoid duplicate external_id conflicts
- Modified SilverPayApiResponse to handle string amounts from API
- Added [JsonIgnore] attributes to computed properties to prevent JSON serialization conflicts
- Fixed test compilation errors (mock service and enum casting issues)
- Updated SilverPay endpoint to http://10.0.0.52:8001/🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Critical fix for £0 order bug:
- When users select a variant and click 'Add to Basket', the confirmvar: callback triggers HandleConfirmVariant
- This method was passing variantId: null to AddItem(), causing cart items to have no variant and price £0
- Now looks up selected variant by name, extracts its ID, and passes it to cart
- Added logging to track which variant is being used
- Also includes CSV variant conversion utility and sample fixed import file
CRITICAL BUG FIX: HandleAddToCart was only checking MultiBuys for price,
never ProductVariants. This caused all variant-based products to use the
base price (£0), resulting in £0 orders.
Changes:
- HandleAddToCart now checks variants FIRST for pricing
- Falls back to multi-buy, then base price (correct priority order)
- Uses proper Product-based AddItem() method to pass variant IDs
- Added logging to track which pricing method is used
- HandleQuickBuy already had correct variant detection (no changes)
Result: Orders now correctly calculate total using variant prices (e.g., £90, £160)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Log variant count and prices when fetching products to diagnose
why variants aren't being detected during add-to-cart flow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added logging to diagnose why orders are created with £0 pricing:
- Log when product has variants and variant selection is shown
- Log WARNING when product has no variants and base price is used
- Helps identify if variants are missing or not being detected
Troubleshooting: Orders showing £0 despite variants having correct prices
🤖 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>
- 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>
- 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
## 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>
- ProductCarouselService: Use Buy button with short IDs for products without images
- CallbackHandler: Fix HandleCategory to use Buy button with short IDs
- MenuBuilder: Remove obsolete SingleProductMenu method
This ensures consistent behavior across all product displays and fixes
the Details button that was broken due to GUID format incompatibility.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Issue**: Order creation failed because TOR proxy was being used for internal
Docker network API calls to littleshop-admin, causing DNS resolution failures.
**Root Cause**:
- All HTTP clients (BotManager, ActivityTracker, ProductCarousel) used
Socks5HttpHandler.Create() which checked Privacy:EnableTor globally
- TOR gateway can only proxy external traffic (to Telegram API)
- Internal Docker network calls to littleshop-admin failed through TOR
**Solution**:
- Updated BotManagerService to use Socks5HttpHandler.CreateDirect()
- Updated BotActivityTracker to use Socks5HttpHandler.CreateDirect()
- Updated ProductCarouselService to use Socks5HttpHandler.CreateDirect()
- TelegramBotService continues using TOR for external Telegram API
- LittleShop.Client respects LittleShop:UseTor = false setting
**Architecture**:
✅ External calls (Telegram API) → TOR for privacy
✅ Internal calls (LittleShop API) → Direct Docker network connection
**Testing**:
- Bot authenticated successfully with LittleShop API (200 OK)
- Telegram Bot API using TOR proxy (socks5://tor-gateway:9050)
- Container: 45eab050eeeca479680966b45742cf140cf7df0ed8e8ab5dc8c9e3e17739c88a
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Issue**: When users clicked "Browse Products" multiple times, Telegram API
rejected the edit request with "message is not modified" error, causing the
browse functionality to appear broken.
**Root Cause**: HandleBrowse method used EditMessageTextAsync directly, which
throws an exception when the message content is identical.
**Solution**:
- Replaced direct EditMessageTextAsync with SafeEditMessageAsync
- SafeEditMessageAsync catches ApiRequestException for "message is not modified"
- Silently handles duplicate edits without user-facing errors
**Testing**:
- Deployed to production (container: e1467c559ff6)
- Bot running as @Slukdevukbot with TOR enabled
- Categories API confirmed working (3 categories, 10 products)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Allows TOR proxy host to be configured via Privacy:TorSocksHost setting.
Defaults to 127.0.0.1 if not specified for backward compatibility.
This enables using external TOR gateways in Docker/container environments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>