B6: PackagePurchaseMethod was NOT reset when exiting Magic Mode.
This caused Guards G1-G3 to permanently block re-purchase.
Changes:
- user.PackagePurchaseMethod = None (allows re-purchase via G1-G3)
- currentCycle.IsCurrentCycle = false (ready for new cycle)
Note: membership.IsActive is intentionally preserved (Q19 — contract is one-time).
Features are preserved — DIFF will be applied on next purchase (Q20).
B1: VerifyGoldenPackagePurchase — DiscountBalance was NOT charged (Amount × 2)
Now charges DiscountBalance same as all other handlers.
B2: VerifyGoldenPackagePurchase — UserPackagePurchase record was NOT created
Now creates UPP with OrderId and TransactionId.
B3: VerifyPackagePurchase — UserPackagePurchase record was NOT created
Now creates UPP after wallet charge.
B4: VerifyBasePackagePayment — UserPackagePurchase record was NOT created
Now creates UPP after wallet change log.
Also: VerifyGolden WalletChangeLog split into 2 logs (Balance + Discount)
to match the pattern used by VerifyPackagePurchase.
Transaction description changed from 'پکیج طلایی' to 'پکیج' (generic).
Impact: Only Daya Loan handler was creating UserPackagePurchase before.
Now all 4 payment paths create it consistently.
Bug: when buying from discount shop, DiscountBalance was deducted from DB
but no UserWalletChangeLog was created — making it invisible in wallet history.
Same issue existed for discount wallet top-up (charge).
Fixed in 3 handlers:
- PlaceOrderCommandHandler (fully paid by discount balance path)
- CompleteOrderPaymentCommandHandler (gateway + discount balance path)
- VerifyDiscountWalletChargeCommandHandler (discount wallet charge)
- Add cms-config.yaml (K8s Secret) for staging and production
- Mount appsettings.{Environment}.json from Secret into /app/
- Update CI/CD pipelines to apply Secret before Deployment
- Remove redundant env vars (Kestrel, FileStorage) — now in config file
- userwallet.proto: added string user_name = 5 to GetAllUserWalletByFilterResponseModel
- UserWalletService: enriches response with user names from Users table
- Proto version bumped to 0.0.183
- ActivateClubMembershipCommandHandler: block activation in Magic mode
- CheckAndProcessDayaLoansCommandHandler: reject Daya after first cycle
- userwallet.proto: add purchase_cycle_count to GetMagicWalletStatusResponse
- UserWalletService: query ClubMembershipCycles count for cycle info
- EF Migration u21: ClubMembershipCycle table + UserWallet Magic fields
- Added ClubMembershipCycle entity and DbSet to IApplicationDbContext.
- Updated VAT rate from 9% to 10% in VatCalculator and related areas.
- Introduced Magic Wallet settings in SystemConstants.
- Enhanced UserWallet entity to support Magic Wallet features.
- Updated TransactionType enum to include Magic Wallet transactions.
- Configured UserWallet to handle Magic Wallet properties in ApplicationDbContext.
- Implemented OrmCommissionCalculationStrategy to exclude Magic Wallet users from commission calculations.
- Added Protobuf definitions for Magic Wallet methods and responses.
- Created PaymentCallbackController endpoint for handling Magic Wallet charge callbacks.
- Updated UserOrderService to manage Magic Wallet state transitions.
- Developed UserWalletService to support Magic Wallet operations.
- Created ChargeMagicWalletCommand and its handler for initiating Magic Wallet charges.
- Implemented VerifyMagicWalletChargeCommand and handler for payment verification.
- Added validation for ChargeMagicWalletCommand.
- Established ClubMembershipCycle configuration for EF Core.
- Introduced WalletMode enum to differentiate between Normal and Magic modes.
Both VerifyBasePackagePayment and VerifyPackagePurchase handlers were
charging DiscountBalance with 1× amount instead of 2×.
Now consistent with DayaLoan and ManualPayment handlers.
- VerifyBasePackagePaymentCommandHandler: DiscountBalance += BasePackageAmount * 2
- VerifyPackagePurchaseCommandHandler: DiscountBalance += order.Amount * 2
- Change logs updated to reflect correct 2× discount value
- CreateNewProductsCommandHandler: inject IInventoryService, auto-init
inventory record with qty=0 when a new regular product is created
(DiscountProduct handler already had this)
- InventoryInitializerService: one-time BackgroundService that runs on
startup, finds existing products without InventoryItem records, and
creates them (migration for legacy products)
- Register InventoryInitializerService in DI
- Added MapDeliveryStatus() to correctly map Domain→Proto enum values
- Domain Pending(1) was incorrectly cast to Proto PROCESSING(1) instead of PROCESSING
- Set DeliveryStatus=Cancelled when payment fails (CompleteOrderPaymentCommandHandler)
- Set DeliveryStatus=Cancelled when order expires (ExpirePendingOrdersService)
Database stores Created timestamps using DateTime.Now (local time).
Background service was comparing with DateTime.UtcNow causing 3.5 hour offset (Iran timezone).
Orders would only expire after ~4 hours instead of 30 minutes.
- Added payment_status field to GetOrderByIdResponse and OrderSummaryDto in proto
- Proto version bumped to 0.0.179
- Added PaymentStatus mapping in DiscountOrderService gRPC responses
- Created ExpirePendingOrdersService: expires pending orders after 30 min, releases inventory
- Registered background service in ConfigureServices
- CompleteOrderPaymentCommandHandler: DeliveryStatus.InTransit → DeliveryStatus.Pending
- PlaceOrderCommandHandler (full discount-balance path): same fix
- Now both stores behave the same: admin must manually update delivery status
- Consistent with regular store flow where order stays Pending after payment
- GetOrderById and GetUserOrders were using Mapster auto-mapping which
could not map 'PaymentStatus' (enum: Success=0) to 'payment_completed' (bool)
- Replaced with manual mapping: PaymentStatus == Success → true
- Also maps DeliveryStatus and all other fields correctly
- Fixed FrontOfficeBaseUrl and CmsBaseUrl to use HTTP for local dev
- Remove PYMSPaymentService.cs and PYMS proto files
- Remove 'pyms' case from DI ConfigureServices
- Remove PYMS config from appsettings
- Switch PaymentProvider to 'zarinpal' (direct integration)
- ZarinPalPaymentService handles sandbox/production, verify, errors
- PYMS deployment/service/ingress removed from K8s
Payment Gateway:
- Add PYMSPaymentService: IPaymentGatewayService via gRPC to PYMS microservice
- Add ZarinPalPaymentService: direct ZarinPal integration (backup)
- Register 'pyms' payment provider in DI ConfigureServices
- Add PYMS proto files (pyms_transaction.proto, pyms_public_messages.proto)
- Fix VerifyDiscountWalletCharge: pass 'OK' as status instead of Authority
- Update appsettings: PaymentProvider=pyms, sandbox mode, merchant ID
Blog System:
- Add BlogCategory, BlogPost, BlogPostImage entities and CQRS
- Add proto files and gRPC services for blog management
- Add Mapster profiles for blog responses
Content Management:
- Add SitePage entity and CQRS for static pages
- Add proto and gRPC service for site pages
Image/File Management:
- Add LocalFileManager with disk storage + base64 serving + FMS fallback
- Add ImagePathResolverInterceptor for gRPC responses
- Add ImageResolverService for explicit image resolution
- Add UploadsController for public file serving with FMS fallback
- Add PaymentCallbackController for discount order payment callbacks
Database:
- Add blog and content entity migrations
- Remove ImagePath MaxLength constraints
- Remove old FileManagementService (replaced by LocalFileManager)