Ver código fonte

trình trả thưởng data và điểm cộng thêm cho app

student 3 semanas atrás
pai
commit
dd25e357d6

+ 4 - 4
KittyFallWs/KittyFallWs/src/com/vas/webservices/KittyFallWs.java

@@ -2539,13 +2539,13 @@ public class KittyFallWs extends WebserviceAbstract {
                     } else {
 
                         int dataConvert = Integer.parseInt(data);
-                        ws.addData(msisdn, dataConvert);
+//                        ws.addData(msisdn, dataConvert);
 
                         ChargeLog chargeLog = new ChargeLog();
                         chargeLog.setFee(-dataConvert);
                         chargeLog.setChargeTime(new Timestamp(System.currentTimeMillis()));
                         chargeLog.setMsisdn(msisdn);
-                        chargeLog.setDescription("Exchange points to data");
+                        chargeLog.setDescription("Exchange data");
                         chargeLog.setStatus(Common.Constant.UNCHARGE_STATUS);
                         db.iInsertChargeLog(chargeLog);
 
@@ -2647,13 +2647,13 @@ public class KittyFallWs extends WebserviceAbstract {
                     } else {
 
                         int dataConvert = Integer.parseInt(point);
-                        ws.addPoint(msisdn, dataConvert);
+//                        ws.addPoint(msisdn, dataConvert);
 
                         ChargeLog chargeLog = new ChargeLog();
                         chargeLog.setFee(-dataConvert);
                         chargeLog.setChargeTime(new Timestamp(System.currentTimeMillis()));
                         chargeLog.setMsisdn(msisdn);
-                        chargeLog.setDescription("Exchange points to data");
+                        chargeLog.setDescription("Exchange points");
                         chargeLog.setStatus(Common.Constant.UNCHARGE_STATUS);
                         db.iInsertChargeLog(chargeLog);
 

+ 9 - 5
Kitty_Fall/Kitty_Fall/Kitty_Fall.Apis/Business/Game/GameBusinessImpl.cs

@@ -873,6 +873,9 @@ namespace Kitty_Fall.Apis.Business.Game
                         responsePoint = FormatPrizeValue(rewardOption.RewardValue);
                     }
 
+                    var waitWorkerPayout = string.Equals(rewardOption.RewardType, "DATA", StringComparison.OrdinalIgnoreCase)
+                        || string.Equals(rewardOption.RewardType, "LOYALTY", StringComparison.OrdinalIgnoreCase);
+
                     dbContext.GameRewardLogs.Add(new GameRewardLog
                     {
                         RewardLogId = await NextDecimalIdAsync("REWARD"),
@@ -888,11 +891,12 @@ namespace Kitty_Fall.Apis.Business.Game
                         RewardType = rewardOption.RewardType,
                         RewardValue = rewardOption.RewardValue,
                         RewardUnit = rewardOption.RewardUnit,
-                        Status = 2,
-                        RequestTime = now,
-                        ResponseTime = now,
-                        ResponseCode = "SUCCESS",
-                        ResponseMessage = "success",
+                        // DATA/LOYALTY can worker goi WS cong thuong that, SCORE la diem noi bo nen thanh cong ngay.
+                        Status = waitWorkerPayout ? (byte)0 : (byte)2,
+                        RequestTime = waitWorkerPayout ? null : now,
+                        ResponseTime = waitWorkerPayout ? null : now,
+                        ResponseCode = waitWorkerPayout ? null : "SUCCESS",
+                        ResponseMessage = waitWorkerPayout ? null : "success",
                         RetryCount = 0,
                         CreatedTime = now
                     });

+ 9 - 1
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Controllers/GuideController.cs

@@ -1,3 +1,5 @@
+using Common.Extension;
+using Common.Http.Apis.Request;
 using Microsoft.AspNetCore.Mvc;
 
 namespace Kitty_Fall.Website.Controllers
@@ -6,7 +8,13 @@ namespace Kitty_Fall.Website.Controllers
     {
         public IActionResult Index()
         {
-            return View();
+            var model = new UserGameActivityProfile
+            {
+                msisdn = HttpContext.Session.GetComplexData<string>("msisdn") ?? "",
+                picture = HttpContext.Session.GetComplexData<string>("profilePicture") ?? ""
+            };
+
+            return View(model);
         }
     }
 }

+ 482 - 2
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Language/Lang.Designer.cs

@@ -1,4 +1,4 @@
-//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
 // <auto-generated>
 //     This code was generated by a tool.
 //     Runtime Version:4.0.30319.42000
@@ -797,5 +797,485 @@ namespace Kitty_Fall.Website.Language {
                 return ResourceManager.GetString("YourGameActivity", resourceCulture);
             }
         }
-    }
+
+        /// <summary>
+        ///   Looks up a localized string for Level.
+        /// </summary>
+        public static string Level {
+            get {
+                return ResourceManager.GetString("Level", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideAriaLabel.
+        /// </summary>
+        public static string GuideAriaLabel {
+            get {
+                return ResourceManager.GetString("GuideAriaLabel", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideTitle.
+        /// </summary>
+        public static string GuideTitle {
+            get {
+                return ResourceManager.GetString("GuideTitle", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideServiceIntroduction.
+        /// </summary>
+        public static string GuideServiceIntroduction {
+            get {
+                return ResourceManager.GetString("GuideServiceIntroduction", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideIntroP1.
+        /// </summary>
+        public static string GuideIntroP1 {
+            get {
+                return ResourceManager.GetString("GuideIntroP1", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideIntroP2.
+        /// </summary>
+        public static string GuideIntroP2 {
+            get {
+                return ResourceManager.GetString("GuideIntroP2", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideIntroP3.
+        /// </summary>
+        public static string GuideIntroP3 {
+            get {
+                return ResourceManager.GetString("GuideIntroP3", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideIntroP4.
+        /// </summary>
+        public static string GuideIntroP4 {
+            get {
+                return ResourceManager.GetString("GuideIntroP4", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideIntroP5.
+        /// </summary>
+        public static string GuideIntroP5 {
+            get {
+                return ResourceManager.GetString("GuideIntroP5", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideIntroP6.
+        /// </summary>
+        public static string GuideIntroP6 {
+            get {
+                return ResourceManager.GetString("GuideIntroP6", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideHowToParticipate.
+        /// </summary>
+        public static string GuideHowToParticipate {
+            get {
+                return ResourceManager.GetString("GuideHowToParticipate", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideCustomersCanPlay.
+        /// </summary>
+        public static string GuideCustomersCanPlay {
+            get {
+                return ResourceManager.GetString("GuideCustomersCanPlay", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideRegistrationChannels.
+        /// </summary>
+        public static string GuideRegistrationChannels {
+            get {
+                return ResourceManager.GetString("GuideRegistrationChannels", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuidePurchaseOrSubscribe.
+        /// </summary>
+        public static string GuidePurchaseOrSubscribe {
+            get {
+                return ResourceManager.GetString("GuidePurchaseOrSubscribe", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideStartPlayingOptions.
+        /// </summary>
+        public static string GuideStartPlayingOptions {
+            get {
+                return ResourceManager.GetString("GuideStartPlayingOptions", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideSubscribeTurnPackages.
+        /// </summary>
+        public static string GuideSubscribeTurnPackages {
+            get {
+                return ResourceManager.GetString("GuideSubscribeTurnPackages", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideOneTimePurchase1.
+        /// </summary>
+        public static string GuideOneTimePurchase1 {
+            get {
+                return ResourceManager.GetString("GuideOneTimePurchase1", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideOneTimePurchase5.
+        /// </summary>
+        public static string GuideOneTimePurchase5 {
+            get {
+                return ResourceManager.GetString("GuideOneTimePurchase5", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideGameOverview.
+        /// </summary>
+        public static string GuideGameOverview {
+            get {
+                return ResourceManager.GetString("GuideGameOverview", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideModeLevels.
+        /// </summary>
+        public static string GuideModeLevels {
+            get {
+                return ResourceManager.GetString("GuideModeLevels", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideTotalLevels.
+        /// </summary>
+        public static string GuideTotalLevels {
+            get {
+                return ResourceManager.GetString("GuideTotalLevels", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideProgressRewards.
+        /// </summary>
+        public static string GuideProgressRewards {
+            get {
+                return ResourceManager.GetString("GuideProgressRewards", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideFailLevel.
+        /// </summary>
+        public static string GuideFailLevel {
+            get {
+                return ResourceManager.GetString("GuideFailLevel", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideRescueOption.
+        /// </summary>
+        public static string GuideRescueOption {
+            get {
+                return ResourceManager.GetString("GuideRescueOption", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideLevelScoreTimeRewardTable.
+        /// </summary>
+        public static string GuideLevelScoreTimeRewardTable {
+            get {
+                return ResourceManager.GetString("GuideLevelScoreTimeRewardTable", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward5.
+        /// </summary>
+        public static string GuideEasyReward5 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward5", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward10.
+        /// </summary>
+        public static string GuideEasyReward10 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward10", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward15.
+        /// </summary>
+        public static string GuideEasyReward15 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward15", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward20.
+        /// </summary>
+        public static string GuideEasyReward20 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward20", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward25.
+        /// </summary>
+        public static string GuideEasyReward25 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward25", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward30.
+        /// </summary>
+        public static string GuideEasyReward30 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward30", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward35.
+        /// </summary>
+        public static string GuideEasyReward35 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward35", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward40.
+        /// </summary>
+        public static string GuideEasyReward40 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward40", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward45.
+        /// </summary>
+        public static string GuideEasyReward45 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward45", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEasyReward50.
+        /// </summary>
+        public static string GuideEasyReward50 {
+            get {
+                return ResourceManager.GetString("GuideEasyReward50", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideMediumReward5.
+        /// </summary>
+        public static string GuideMediumReward5 {
+            get {
+                return ResourceManager.GetString("GuideMediumReward5", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideHardReward5.
+        /// </summary>
+        public static string GuideHardReward5 {
+            get {
+                return ResourceManager.GetString("GuideHardReward5", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideMonthlyRewardEligibility.
+        /// </summary>
+        public static string GuideMonthlyRewardEligibility {
+            get {
+                return ResourceManager.GetString("GuideMonthlyRewardEligibility", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuidePlayersRequired.
+        /// </summary>
+        public static string GuidePlayersRequired {
+            get {
+                return ResourceManager.GetString("GuidePlayersRequired", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideConditions.
+        /// </summary>
+        public static string GuideConditions {
+            get {
+                return ResourceManager.GetString("GuideConditions", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideValue.
+        /// </summary>
+        public static string GuideValue {
+            get {
+                return ResourceManager.GetString("GuideValue", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideTotalScore.
+        /// </summary>
+        public static string GuideTotalScore {
+            get {
+                return ResourceManager.GetString("GuideTotalScore", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideTotalScoreCondition.
+        /// </summary>
+        public static string GuideTotalScoreCondition {
+            get {
+                return ResourceManager.GetString("GuideTotalScoreCondition", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideMonthlyRewardChance.
+        /// </summary>
+        public static string GuideMonthlyRewardChance {
+            get {
+                return ResourceManager.GetString("GuideMonthlyRewardChance", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideRewardDistributionMechanism.
+        /// </summary>
+        public static string GuideRewardDistributionMechanism {
+            get {
+                return ResourceManager.GetString("GuideRewardDistributionMechanism", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideEndOfMonth.
+        /// </summary>
+        public static string GuideEndOfMonth {
+            get {
+                return ResourceManager.GetString("GuideEndOfMonth", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideCollectEligibleUsers.
+        /// </summary>
+        public static string GuideCollectEligibleUsers {
+            get {
+                return ResourceManager.GetString("GuideCollectEligibleUsers", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideRandomDraw.
+        /// </summary>
+        public static string GuideRandomDraw {
+            get {
+                return ResourceManager.GetString("GuideRandomDraw", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideTicketsByPoint.
+        /// </summary>
+        public static string GuideTicketsByPoint {
+            get {
+                return ResourceManager.GetString("GuideTicketsByPoint", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideDrawTicketsBasedOnPoints.
+        /// </summary>
+        public static string GuideDrawTicketsBasedOnPoints {
+            get {
+                return ResourceManager.GetString("GuideDrawTicketsBasedOnPoints", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideTotalScoreMonth.
+        /// </summary>
+        public static string GuideTotalScoreMonth {
+            get {
+                return ResourceManager.GetString("GuideTotalScoreMonth", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideNumberOfTicket.
+        /// </summary>
+        public static string GuideNumberOfTicket {
+            get {
+                return ResourceManager.GetString("GuideNumberOfTicket", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideOneTicket.
+        /// </summary>
+        public static string GuideOneTicket {
+            get {
+                return ResourceManager.GetString("GuideOneTicket", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideTwoTickets.
+        /// </summary>
+        public static string GuideTwoTickets {
+            get {
+                return ResourceManager.GetString("GuideTwoTickets", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideThreeTickets.
+        /// </summary>
+        public static string GuideThreeTickets {
+            get {
+                return ResourceManager.GetString("GuideThreeTickets", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideFiveTickets.
+        /// </summary>
+        public static string GuideFiveTickets {
+            get {
+                return ResourceManager.GetString("GuideFiveTickets", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideSevenTickets.
+        /// </summary>
+        public static string GuideSevenTickets {
+            get {
+                return ResourceManager.GetString("GuideSevenTickets", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideTenTickets.
+        /// </summary>
+        public static string GuideTenTickets {
+            get {
+                return ResourceManager.GetString("GuideTenTickets", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideMonthlyAnnouncement.
+        /// </summary>
+        public static string GuideMonthlyAnnouncement {
+            get {
+                return ResourceManager.GetString("GuideMonthlyAnnouncement", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for GuideMonthlyPrizeLimit.
+        /// </summary>
+        public static string GuideMonthlyPrizeLimit {
+            get {
+                return ResourceManager.GetString("GuideMonthlyPrizeLimit", resourceCulture);
+            }
+        }    }
 }

+ 180 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Language/Lang.resx

@@ -363,4 +363,184 @@
   <data name="NoMonthlyWinnersFound" xml:space="preserve">
     <value>No monthly winners found</value>
   </data>
+  <data name="Level" xml:space="preserve">
+    <value>Level</value>
+  </data>
+  <data name="GuideAriaLabel" xml:space="preserve">
+    <value>Kitty Fall service guide</value>
+  </data>
+  <data name="GuideTitle" xml:space="preserve">
+    <value>Proposed Scenario for&lt;br&gt;Kitty Fall Service</value>
+  </data>
+  <data name="GuideServiceIntroduction" xml:space="preserve">
+    <value>Service Introduction</value>
+  </data>
+  <data name="GuideIntroP1" xml:space="preserve">
+    <value>Kitty Fall is a casual puzzle game designed with simple, easy-to-access gameplay yet offering a high level of challenge. It provides light entertainment while giving players opportunities to earn attractive rewards through a play-earn-redeem mechanism.</value>
+  </data>
+  <data name="GuideIntroP2" xml:space="preserve">
+    <value>Players join a journey by controlling number blocks to collect the correct number of cats of the same color. After completing each level, players receive reward points, extra turns, or support items, along with chances to unlock valuable rewards such as data packages, call minutes, and Loyalty points.</value>
+  </data>
+  <data name="GuideIntroP3" xml:space="preserve">
+    <value>The game is designed for mass mobile users, especially in developing markets where users prefer lightweight, easy-to-play, highly interactive games that deliver real value.</value>
+  </data>
+  <data name="GuideIntroP4" xml:space="preserve">
+    <value>With a system of levels of increasing difficulty, Kitty Fall creates a clear progression journey, motivating players to continue conquering higher levels for greater rewards, thereby increasing retention and usage time.</value>
+  </data>
+  <data name="GuideIntroP5" xml:space="preserve">
+    <value>The service integrates effective retention and monetization mechanisms such as daily play limits, purchasing additional turns, using Rescue when close to completing a level, and a point-based reward redemption system, ensuring a balance between user experience and business objectives.</value>
+  </data>
+  <data name="GuideIntroP6" xml:space="preserve">
+    <value>Kitty Fall is not just an entertainment game but also a gamification tool that helps Mytel enhance customer engagement, drive data usage, and increase revenue from value-added services.</value>
+  </data>
+  <data name="GuideHowToParticipate" xml:space="preserve">
+    <value>How to Participate</value>
+  </data>
+  <data name="GuideCustomersCanPlay" xml:space="preserve">
+    <value>Customers can play the game via WEB/APP channels.</value>
+  </data>
+  <data name="GuideRegistrationChannels" xml:space="preserve">
+    <value>Service registration is available through multiple channels: SMS / USSD / WEB / APP.</value>
+  </data>
+  <data name="GuidePurchaseOrSubscribe" xml:space="preserve">
+    <value>Purchase or subscribe to play turns</value>
+  </data>
+  <data name="GuideStartPlayingOptions" xml:space="preserve">
+    <value>To start playing, users need available turns and can choose one of the following options:</value>
+  </data>
+  <data name="GuideSubscribeTurnPackages" xml:space="preserve">
+    <value>Subscribe to turn packages: 179 MMK / 5 turn</value>
+  </data>
+  <data name="GuideOneTimePurchase1" xml:space="preserve">
+    <value>One-time purchase: 89 MMK / 1 turn</value>
+  </data>
+  <data name="GuideOneTimePurchase5" xml:space="preserve">
+    <value>189 MMK / 5 turn</value>
+  </data>
+  <data name="GuideGameOverview" xml:space="preserve">
+    <value>Game Overview</value>
+  </data>
+  <data name="GuideModeLevels" xml:space="preserve">
+    <value>Each mode consists of 50 levels, with differences in gameplay time, scoring, and rewards at each level.</value>
+  </data>
+  <data name="GuideTotalLevels" xml:space="preserve">
+    <value>The game includes a total of 50 levels with increasing difficulty, allowing players to progressively experience and challenge themselves.</value>
+  </data>
+  <data name="GuideProgressRewards" xml:space="preserve">
+    <value>As players progress and complete each level, they can earn reward points, and at key milestones, they will receive direct rewards from the service.</value>
+  </data>
+  <data name="GuideFailLevel" xml:space="preserve">
+    <value>If a player fails to complete a level within the required time, they will lose a turn, the game will stop, and they must restart from the beginning.</value>
+  </data>
+  <data name="GuideRescueOption" xml:space="preserve">
+    <value>If a player is unable to complete a level, they can purchase a Rescue option (maximum 1 time per turn) at a cost of 79 MMK.</value>
+  </data>
+  <data name="GuideLevelScoreTimeRewardTable" xml:space="preserve">
+    <value>Level - Score - Time - Reward Table</value>
+  </data>
+  <data name="GuideEasyReward5" xml:space="preserve">
+    <value>5MB Data / 3 loyalty / 20 score</value>
+  </data>
+  <data name="GuideEasyReward10" xml:space="preserve">
+    <value>10MB Data / 5 loyalty / 40 score</value>
+  </data>
+  <data name="GuideEasyReward15" xml:space="preserve">
+    <value>20MB Data / 10 loyalty / 70 score</value>
+  </data>
+  <data name="GuideEasyReward20" xml:space="preserve">
+    <value>40MB Data / 10 Call minutes / 100 score</value>
+  </data>
+  <data name="GuideEasyReward25" xml:space="preserve">
+    <value>70MB Data / 20 loyalty / 140 score</value>
+  </data>
+  <data name="GuideEasyReward30" xml:space="preserve">
+    <value>100MB Data / 30 loyalty / 180 score</value>
+  </data>
+  <data name="GuideEasyReward35" xml:space="preserve">
+    <value>120MB Data / 40 loyalty / 230 score</value>
+  </data>
+  <data name="GuideEasyReward40" xml:space="preserve">
+    <value>150MB Data / 50 loyalty / 300 score</value>
+  </data>
+  <data name="GuideEasyReward45" xml:space="preserve">
+    <value>180MB Data / 60 loyalty / 350 score</value>
+  </data>
+  <data name="GuideEasyReward50" xml:space="preserve">
+    <value>200MB Data / 80 loyalty / 500 score</value>
+  </data>
+  <data name="GuideMediumReward5" xml:space="preserve">
+    <value>10MB Data / 5 loyalty / 25 score</value>
+  </data>
+  <data name="GuideHardReward5" xml:space="preserve">
+    <value>15MB Data / 7 loyalty / 25 score</value>
+  </data>
+  <data name="GuideMonthlyRewardEligibility" xml:space="preserve">
+    <value>Monthly Reward Eligibility Conditions</value>
+  </data>
+  <data name="GuidePlayersRequired" xml:space="preserve">
+    <value>Players are required to achieve:</value>
+  </data>
+  <data name="GuideConditions" xml:space="preserve">
+    <value>Conditions</value>
+  </data>
+  <data name="GuideValue" xml:space="preserve">
+    <value>Value</value>
+  </data>
+  <data name="GuideTotalScore" xml:space="preserve">
+    <value>Total score</value>
+  </data>
+  <data name="GuideTotalScoreCondition" xml:space="preserve">
+    <value>&amp;ge; 5,000 score / month</value>
+  </data>
+  <data name="GuideMonthlyRewardChance" xml:space="preserve">
+    <value>All users who meet the requirements have the chance to participate in the monthly reward.</value>
+  </data>
+  <data name="GuideRewardDistributionMechanism" xml:space="preserve">
+    <value>Reward Distribution Mechanism</value>
+  </data>
+  <data name="GuideEndOfMonth" xml:space="preserve">
+    <value>At the end of the month</value>
+  </data>
+  <data name="GuideCollectEligibleUsers" xml:space="preserve">
+    <value>Collect all eligible users</value>
+  </data>
+  <data name="GuideRandomDraw" xml:space="preserve">
+    <value>Conduct a random draw</value>
+  </data>
+  <data name="GuideTicketsByPoint" xml:space="preserve">
+    <value>Each user receives a number of tickets corresponding to their point level</value>
+  </data>
+  <data name="GuideDrawTicketsBasedOnPoints" xml:space="preserve">
+    <value>Draw Tickets based on points</value>
+  </data>
+  <data name="GuideTotalScoreMonth" xml:space="preserve">
+    <value>Total score month</value>
+  </data>
+  <data name="GuideNumberOfTicket" xml:space="preserve">
+    <value>Number of ticket</value>
+  </data>
+  <data name="GuideOneTicket" xml:space="preserve">
+    <value>1 ticket</value>
+  </data>
+  <data name="GuideTwoTickets" xml:space="preserve">
+    <value>2 tickets</value>
+  </data>
+  <data name="GuideThreeTickets" xml:space="preserve">
+    <value>3 tickets</value>
+  </data>
+  <data name="GuideFiveTickets" xml:space="preserve">
+    <value>5 tickets</value>
+  </data>
+  <data name="GuideSevenTickets" xml:space="preserve">
+    <value>7 tickets</value>
+  </data>
+  <data name="GuideTenTickets" xml:space="preserve">
+    <value>10 tickets</value>
+  </data>
+  <data name="GuideMonthlyAnnouncement" xml:space="preserve">
+    <value>On the 1st of each month at 5:00 AM, the system will announce the lucky customers who win the prizes.</value>
+  </data>
+  <data name="GuideMonthlyPrizeLimit" xml:space="preserve">
+    <value>Each subscriber is eligible to receive a monthly prize only once within the most recent 3-month period.</value>
+  </data>
 </root>

+ 180 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Language/Lang.vi.resx

@@ -363,4 +363,184 @@
   <data name="NoMonthlyWinnersFound" xml:space="preserve">
     <value>No monthly winners found</value>
   </data>
+  <data name="Level" xml:space="preserve">
+    <value>Level</value>
+  </data>
+  <data name="GuideAriaLabel" xml:space="preserve">
+    <value>Kitty Fall service guide</value>
+  </data>
+  <data name="GuideTitle" xml:space="preserve">
+    <value>Proposed Scenario for&lt;br&gt;Kitty Fall Service</value>
+  </data>
+  <data name="GuideServiceIntroduction" xml:space="preserve">
+    <value>Service Introduction</value>
+  </data>
+  <data name="GuideIntroP1" xml:space="preserve">
+    <value>Kitty Fall is a casual puzzle game designed with simple, easy-to-access gameplay yet offering a high level of challenge. It provides light entertainment while giving players opportunities to earn attractive rewards through a play-earn-redeem mechanism.</value>
+  </data>
+  <data name="GuideIntroP2" xml:space="preserve">
+    <value>Players join a journey by controlling number blocks to collect the correct number of cats of the same color. After completing each level, players receive reward points, extra turns, or support items, along with chances to unlock valuable rewards such as data packages, call minutes, and Loyalty points.</value>
+  </data>
+  <data name="GuideIntroP3" xml:space="preserve">
+    <value>The game is designed for mass mobile users, especially in developing markets where users prefer lightweight, easy-to-play, highly interactive games that deliver real value.</value>
+  </data>
+  <data name="GuideIntroP4" xml:space="preserve">
+    <value>With a system of levels of increasing difficulty, Kitty Fall creates a clear progression journey, motivating players to continue conquering higher levels for greater rewards, thereby increasing retention and usage time.</value>
+  </data>
+  <data name="GuideIntroP5" xml:space="preserve">
+    <value>The service integrates effective retention and monetization mechanisms such as daily play limits, purchasing additional turns, using Rescue when close to completing a level, and a point-based reward redemption system, ensuring a balance between user experience and business objectives.</value>
+  </data>
+  <data name="GuideIntroP6" xml:space="preserve">
+    <value>Kitty Fall is not just an entertainment game but also a gamification tool that helps Mytel enhance customer engagement, drive data usage, and increase revenue from value-added services.</value>
+  </data>
+  <data name="GuideHowToParticipate" xml:space="preserve">
+    <value>How to Participate</value>
+  </data>
+  <data name="GuideCustomersCanPlay" xml:space="preserve">
+    <value>Customers can play the game via WEB/APP channels.</value>
+  </data>
+  <data name="GuideRegistrationChannels" xml:space="preserve">
+    <value>Service registration is available through multiple channels: SMS / USSD / WEB / APP.</value>
+  </data>
+  <data name="GuidePurchaseOrSubscribe" xml:space="preserve">
+    <value>Purchase or subscribe to play turns</value>
+  </data>
+  <data name="GuideStartPlayingOptions" xml:space="preserve">
+    <value>To start playing, users need available turns and can choose one of the following options:</value>
+  </data>
+  <data name="GuideSubscribeTurnPackages" xml:space="preserve">
+    <value>Subscribe to turn packages: 179 MMK / 5 turn</value>
+  </data>
+  <data name="GuideOneTimePurchase1" xml:space="preserve">
+    <value>One-time purchase: 89 MMK / 1 turn</value>
+  </data>
+  <data name="GuideOneTimePurchase5" xml:space="preserve">
+    <value>189 MMK / 5 turn</value>
+  </data>
+  <data name="GuideGameOverview" xml:space="preserve">
+    <value>Game Overview</value>
+  </data>
+  <data name="GuideModeLevels" xml:space="preserve">
+    <value>Each mode consists of 50 levels, with differences in gameplay time, scoring, and rewards at each level.</value>
+  </data>
+  <data name="GuideTotalLevels" xml:space="preserve">
+    <value>The game includes a total of 50 levels with increasing difficulty, allowing players to progressively experience and challenge themselves.</value>
+  </data>
+  <data name="GuideProgressRewards" xml:space="preserve">
+    <value>As players progress and complete each level, they can earn reward points, and at key milestones, they will receive direct rewards from the service.</value>
+  </data>
+  <data name="GuideFailLevel" xml:space="preserve">
+    <value>If a player fails to complete a level within the required time, they will lose a turn, the game will stop, and they must restart from the beginning.</value>
+  </data>
+  <data name="GuideRescueOption" xml:space="preserve">
+    <value>If a player is unable to complete a level, they can purchase a Rescue option (maximum 1 time per turn) at a cost of 79 MMK.</value>
+  </data>
+  <data name="GuideLevelScoreTimeRewardTable" xml:space="preserve">
+    <value>Level - Score - Time - Reward Table</value>
+  </data>
+  <data name="GuideEasyReward5" xml:space="preserve">
+    <value>5MB Data / 3 loyalty / 20 score</value>
+  </data>
+  <data name="GuideEasyReward10" xml:space="preserve">
+    <value>10MB Data / 5 loyalty / 40 score</value>
+  </data>
+  <data name="GuideEasyReward15" xml:space="preserve">
+    <value>20MB Data / 10 loyalty / 70 score</value>
+  </data>
+  <data name="GuideEasyReward20" xml:space="preserve">
+    <value>40MB Data / 10 Call minutes / 100 score</value>
+  </data>
+  <data name="GuideEasyReward25" xml:space="preserve">
+    <value>70MB Data / 20 loyalty / 140 score</value>
+  </data>
+  <data name="GuideEasyReward30" xml:space="preserve">
+    <value>100MB Data / 30 loyalty / 180 score</value>
+  </data>
+  <data name="GuideEasyReward35" xml:space="preserve">
+    <value>120MB Data / 40 loyalty / 230 score</value>
+  </data>
+  <data name="GuideEasyReward40" xml:space="preserve">
+    <value>150MB Data / 50 loyalty / 300 score</value>
+  </data>
+  <data name="GuideEasyReward45" xml:space="preserve">
+    <value>180MB Data / 60 loyalty / 350 score</value>
+  </data>
+  <data name="GuideEasyReward50" xml:space="preserve">
+    <value>200MB Data / 80 loyalty / 500 score</value>
+  </data>
+  <data name="GuideMediumReward5" xml:space="preserve">
+    <value>10MB Data / 5 loyalty / 25 score</value>
+  </data>
+  <data name="GuideHardReward5" xml:space="preserve">
+    <value>15MB Data / 7 loyalty / 25 score</value>
+  </data>
+  <data name="GuideMonthlyRewardEligibility" xml:space="preserve">
+    <value>Monthly Reward Eligibility Conditions</value>
+  </data>
+  <data name="GuidePlayersRequired" xml:space="preserve">
+    <value>Players are required to achieve:</value>
+  </data>
+  <data name="GuideConditions" xml:space="preserve">
+    <value>Conditions</value>
+  </data>
+  <data name="GuideValue" xml:space="preserve">
+    <value>Value</value>
+  </data>
+  <data name="GuideTotalScore" xml:space="preserve">
+    <value>Total score</value>
+  </data>
+  <data name="GuideTotalScoreCondition" xml:space="preserve">
+    <value>&amp;ge; 5,000 score / month</value>
+  </data>
+  <data name="GuideMonthlyRewardChance" xml:space="preserve">
+    <value>All users who meet the requirements have the chance to participate in the monthly reward.</value>
+  </data>
+  <data name="GuideRewardDistributionMechanism" xml:space="preserve">
+    <value>Reward Distribution Mechanism</value>
+  </data>
+  <data name="GuideEndOfMonth" xml:space="preserve">
+    <value>At the end of the month</value>
+  </data>
+  <data name="GuideCollectEligibleUsers" xml:space="preserve">
+    <value>Collect all eligible users</value>
+  </data>
+  <data name="GuideRandomDraw" xml:space="preserve">
+    <value>Conduct a random draw</value>
+  </data>
+  <data name="GuideTicketsByPoint" xml:space="preserve">
+    <value>Each user receives a number of tickets corresponding to their point level</value>
+  </data>
+  <data name="GuideDrawTicketsBasedOnPoints" xml:space="preserve">
+    <value>Draw Tickets based on points</value>
+  </data>
+  <data name="GuideTotalScoreMonth" xml:space="preserve">
+    <value>Total score month</value>
+  </data>
+  <data name="GuideNumberOfTicket" xml:space="preserve">
+    <value>Number of ticket</value>
+  </data>
+  <data name="GuideOneTicket" xml:space="preserve">
+    <value>1 ticket</value>
+  </data>
+  <data name="GuideTwoTickets" xml:space="preserve">
+    <value>2 tickets</value>
+  </data>
+  <data name="GuideThreeTickets" xml:space="preserve">
+    <value>3 tickets</value>
+  </data>
+  <data name="GuideFiveTickets" xml:space="preserve">
+    <value>5 tickets</value>
+  </data>
+  <data name="GuideSevenTickets" xml:space="preserve">
+    <value>7 tickets</value>
+  </data>
+  <data name="GuideTenTickets" xml:space="preserve">
+    <value>10 tickets</value>
+  </data>
+  <data name="GuideMonthlyAnnouncement" xml:space="preserve">
+    <value>On the 1st of each month at 5:00 AM, the system will announce the lucky customers who win the prizes.</value>
+  </data>
+  <data name="GuideMonthlyPrizeLimit" xml:space="preserve">
+    <value>Each subscriber is eligible to receive a monthly prize only once within the most recent 3-month period.</value>
+  </data>
 </root>

+ 73 - 68
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Views/Guide/Index.cshtml

@@ -1,91 +1,96 @@
-@{
+@model Common.Http.Apis.Request.UserGameActivityProfile
+@{
     ViewData["Title"] = "Kitty Fall - Guide";
+    var profile = Model ?? new Common.Http.Apis.Request.UserGameActivityProfile();
+    var profilePicture = string.IsNullOrWhiteSpace(profile.picture)
+        ? Url.Content("~/kitty/assets/kitty-fall/avatar.png")
+        : profile.picture;
 }
 
-<main class="kitty-app is-winner-screen is-guide-screen" aria-label="Kitty Fall guide screen">
+<main class="kitty-app is-winner-screen is-guide-screen" aria-label="@Lang.GuideAriaLabel">
     <img class="kitty-bg" src="~/kitty/assets/kitty-fall/bg.png" alt="">
     <div class="kitty-bg-fade"></div>
 
     <header class="topbar">
         <div class="profile">
-            <img class="profile-avatar" src="~/kitty/assets/kitty-fall/avatar.png" alt="Player avatar">
+            <img class="profile-avatar" src="@profilePicture" alt="@Lang.PlayerAvatar">
             <div class="profile-copy">
                 <strong>@Lang.Hello</strong>
-                <span>0912 345 678</span>
+                <span>@profile.msisdn</span>
             </div>
         </div>
-        <button class="menu-button" aria-label="Open menu">
+        <button class="menu-button" aria-label="@Lang.OpenMenu">
             <span></span>
             <span></span>
             <span></span>
         </button>
     </header>
 
-    <section class="guide-screen" aria-label="Kitty Fall service guide">
+    <section class="guide-screen" aria-label="@Lang.GuideAriaLabel">
         <article class="guide-panel">
             <div class="guide-content">
-                <h1>Proposed Scenario for<br>Kitty Fall Service</h1>
+                <h1>@Html.Raw(Lang.GuideTitle)</h1>
                 <img class="guide-logo" src="~/kitty/assets/kitty-fall/logo.png" alt="Kittyfall">
 
                 <section class="guide-section">
-                    <h2><b>1</b><span>Service Introduction</span></h2>
-                    <p>Kitty Fall is a casual puzzle game designed with simple, easy-to-access gameplay yet offering a high level of challenge. It provides light entertainment while giving players opportunities to earn attractive rewards through a play-earn-redeem mechanism.</p>
-                    <p>Players join a journey to restore the “connected city” by controlling number blocks to collect the correct number of cats of the same color. After completing each level, players receive reward points, extra turns, or support items (Rescue), along with chances to unlock valuable rewards such as data packages, call minutes, and Loyalty points.</p>
-                    <p>The game is designed for mass mobile users, especially in developing markets where users prefer lightweight, easy-to-play, highly interactive games that deliver real value.</p>
+                    <h2><b>1</b><span>@Lang.GuideServiceIntroduction</span></h2>
+                    <p>@Lang.GuideIntroP1</p>
+                    <p>@Lang.GuideIntroP2</p>
+                    <p>@Lang.GuideIntroP3</p>
                     <img class="guide-cat" src="~/kitty/assets/kitty-fall/cat.png" alt="">
-                    <p>With a system of 25 levels of increasing difficulty, Kitty Fall creates a clear progression journey, motivating players to continue conquering higher levels for greater rewards, thereby increasing retention and usage time.</p>
-                    <p>The service integrates effective retention and monetization mechanisms such as daily play limits, purchasing additional turns, using Rescue when close to completing a level, and a point-based reward redemption system, ensuring a balance between user experience and business objectives.</p>
-                    <p>Kitty Fall is not just an entertainment game but also a gamification tool that helps Mytel enhance customer engagement, drive data usage, and increase revenue from value-added services (VAS).</p>
+                    <p>@Lang.GuideIntroP4</p>
+                    <p>@Lang.GuideIntroP5</p>
+                    <p>@Lang.GuideIntroP6</p>
                 </section>
 
                 <section class="guide-section">
-                    <h2><b>2</b><span>How to Participate</span></h2>
-                    <p><strong>Customers can play the game via WEB/APP channels.</strong></p>
-                    <p><strong>Service registration is available through multiple channels: SMS / USSD / WEB / APP.</strong></p>
-                    <p><strong>Purchase or subscribe to play turns</strong></p>
-                    <p>To start playing, users need available turns and can choose one of the following options:</p>
+                    <h2><b>2</b><span>@Lang.GuideHowToParticipate</span></h2>
+                    <p><strong>@Lang.GuideCustomersCanPlay</strong></p>
+                    <p><strong>@Lang.GuideRegistrationChannels</strong></p>
+                    <p><strong>@Lang.GuidePurchaseOrSubscribe</strong></p>
+                    <p>@Lang.GuideStartPlayingOptions</p>
                     <ul>
-                        <li>Subscribe to turn packages: 179 MMK / 5 turn</li>
-                        <li>One-time purchase: 89 MMK / 1 turn</li>
-                        <li>189 MMK / 5 turn</li>
+                        <li>@Lang.GuideSubscribeTurnPackages</li>
+                        <li>@Lang.GuideOneTimePurchase1</li>
+                        <li>@Lang.GuideOneTimePurchase5</li>
                     </ul>
                     <div class="guide-package">
                         <img src="~/kitty/assets/kitty-fall/gift.png" alt="">
                         <strong>179</strong>
                         <span>MMK/Day</span>
-                        <button type="button">Subscribe Now</button>
+                        <button type="button">@Lang.SubscribeNow</button>
                     </div>
                 </section>
 
                 <section class="guide-section">
-                    <h2><b>3</b><span>Game Overview</span></h2>
+                    <h2><b>3</b><span>@Lang.GuideGameOverview</span></h2>
                     <p>@Lang.GameDifficultyLevelGuide</p>
-                    <p>Each mode consists of 50 levels, with differences in gameplay time, scoring, and rewards at each level.</p>
-                    <p>The game includes a total of 50 levels with increasing difficulty, allowing players to progressively experience and challenge themselves.</p>
-                    <p>As players progress and complete each level, they can earn reward points, and at key milestones, they will receive direct rewards from the service.</p>
-                    <p>If a player fails to complete a level within the required time, they will lose a turn, the game will stop, and they must restart from the beginning.</p>
-                    <p>If a player is unable to complete a level, they can purchase a Rescue option (maximum 1 time per turn) at a cost of 79 MMK.</p>
+                    <p>@Lang.GuideModeLevels</p>
+                    <p>@Lang.GuideTotalLevels</p>
+                    <p>@Lang.GuideProgressRewards</p>
+                    <p>@Lang.GuideFailLevel</p>
+                    <p>@Lang.GuideRescueOption</p>
 
-                    <h3>Level - Score - Time - Reward Table</h3>
+                    <h3>@Lang.GuideLevelScoreTimeRewardTable</h3>
                     <div class="guide-table-wrap">
                         <table class="guide-table">
                             <caption>@Lang.Easy @Lang.Mode</caption>
                             <thead>
-                                <tr><th>Level</th><th>Score</th><th>Time</th><th>Reward</th></tr>
+                                <tr><th>@Lang.Level</th><th>@Lang.Score</th><th>@Lang.Time</th><th>@Lang.Rewards</th></tr>
                             </thead>
                             <tbody>
                                 <tr><td>1-4</td><td>10</td><td>150s</td><td>-</td></tr>
-                                <tr><td>5</td><td>-</td><td>150s</td><td>5MB Data / 3 loyalty / 20 score</td></tr>
+                                <tr><td>5</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward5</td></tr>
                                 <tr><td>6-9</td><td>15</td><td>150s</td><td>-</td></tr>
-                                <tr><td>10</td><td>-</td><td>150s</td><td>10MB Data / 5 loyalty / 40 score</td></tr>
-                                <tr><td>15</td><td>-</td><td>150s</td><td>20MB Data / 10 loyalty / 70 score</td></tr>
-                                <tr><td>20</td><td>-</td><td>150s</td><td>40MB Data / 10 Call minutes / 100 score</td></tr>
-                                <tr><td>25</td><td>-</td><td>150s</td><td>70MB Data / 20 loyalty / 140 score</td></tr>
-                                <tr><td>30</td><td>-</td><td>150s</td><td>100MB Data / 30 loyalty / 180 score</td></tr>
-                                <tr><td>35</td><td>-</td><td>150s</td><td>120MB Data / 40 loyalty / 230 score</td></tr>
-                                <tr><td>40</td><td>-</td><td>150s</td><td>150MB Data / 50 loyalty / 300 score</td></tr>
-                                <tr><td>45</td><td>-</td><td>150s</td><td>180MB Data / 60 loyalty / 350 score</td></tr>
-                                <tr><td>50</td><td>-</td><td>150s</td><td>200MB Data / 80 loyalty / 500 score</td></tr>
+                                <tr><td>10</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward10</td></tr>
+                                <tr><td>15</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward15</td></tr>
+                                <tr><td>20</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward20</td></tr>
+                                <tr><td>25</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward25</td></tr>
+                                <tr><td>30</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward30</td></tr>
+                                <tr><td>35</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward35</td></tr>
+                                <tr><td>40</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward40</td></tr>
+                                <tr><td>45</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward45</td></tr>
+                                <tr><td>50</td><td>-</td><td>150s</td><td>@Lang.GuideEasyReward50</td></tr>
                             </tbody>
                         </table>
                     </div>
@@ -94,11 +99,11 @@
                         <table class="guide-table is-compact">
                             <caption>@Lang.Medium @Lang.Mode</caption>
                             <thead>
-                                <tr><th>Level</th><th>Score</th><th>Time</th><th>Reward</th></tr>
+                                <tr><th>@Lang.Level</th><th>@Lang.Score</th><th>@Lang.Time</th><th>@Lang.Rewards</th></tr>
                             </thead>
                             <tbody>
                                 <tr><td>1-4</td><td>10</td><td>150s</td><td>-</td></tr>
-                                <tr><td>5</td><td>-</td><td>150s</td><td>10MB Data / 5 loyalty / 25 score</td></tr>
+                                <tr><td>5</td><td>-</td><td>150s</td><td>@Lang.GuideMediumReward5</td></tr>
                             </tbody>
                         </table>
                     </div>
@@ -107,65 +112,65 @@
                         <table class="guide-table is-compact">
                             <caption>@Lang.Hard @Lang.Mode</caption>
                             <thead>
-                                <tr><th>Level</th><th>Score</th><th>Time</th><th>Reward</th></tr>
+                                <tr><th>@Lang.Level</th><th>@Lang.Score</th><th>@Lang.Time</th><th>@Lang.Rewards</th></tr>
                             </thead>
                             <tbody>
                                 <tr><td>1-4</td><td>10</td><td>150s</td><td>-</td></tr>
-                                <tr><td>5</td><td>-</td><td>150s</td><td>15MB Data / 7 loyalty / 25 score</td></tr>
+                                <tr><td>5</td><td>-</td><td>150s</td><td>@Lang.GuideHardReward5</td></tr>
                             </tbody>
                         </table>
                     </div>
                 </section>
 
                 <section class="guide-section">
-                    <h2><b>4</b><span>Reward</span></h2>
-                    <p><strong>Monthly Reward Eligibility Conditions</strong><br>Players are required to achieve:</p>
+                    <h2><b>4</b><span>@Lang.Rewards</span></h2>
+                    <p><strong>@Lang.GuideMonthlyRewardEligibility</strong><br>@Lang.GuidePlayersRequired</p>
                     <div class="guide-table-wrap">
                         <table class="guide-table is-reward">
                             <thead>
-                                <tr><th>Conditions</th><th>Value</th></tr>
+                                <tr><th>@Lang.GuideConditions</th><th>@Lang.GuideValue</th></tr>
                             </thead>
                             <tbody>
-                                <tr><td>Total score</td><td>&ge; 5,000 score / month</td></tr>
+                                <tr><td>@Lang.GuideTotalScore</td><td>@Html.Raw(Lang.GuideTotalScoreCondition)</td></tr>
                             </tbody>
                         </table>
                     </div>
-                    <p>All users who meet the requirements have the chance to participate in the monthly reward.</p>
+                    <p>@Lang.GuideMonthlyRewardChance</p>
                     <ul>
-                        <li>Reward Distribution Mechanism</li>
-                        <li>At the end of the month</li>
-                        <li>Collect all eligible users</li>
-                        <li>Conduct a random draw</li>
-                        <li>Each user receives a number of “tickets” corresponding to their point level</li>
+                        <li>@Lang.GuideRewardDistributionMechanism</li>
+                        <li>@Lang.GuideEndOfMonth</li>
+                        <li>@Lang.GuideCollectEligibleUsers</li>
+                        <li>@Lang.GuideRandomDraw</li>
+                        <li>@Lang.GuideTicketsByPoint</li>
                     </ul>
 
                     <div class="guide-winner-card">
                         <img src="~/kitty/assets/kitty-fall/winner-monthly.png" alt="">
                         <div>
-                            <h4>Monthly Winners</h4>
-                            <p>View monthly prize winners and special rewards.</p>
-                            <a href="/MonthlyWinners">View monthly winners</a>
+                            <h4>@Lang.MonthlyWinners</h4>
+                            <p>@Lang.MonthlyWinnersDescription</p>
+                            <a href="/MonthlyWinners">@Lang.ViewMonthlyWinners</a>
                         </div>
                     </div>
 
-                    <p><strong>Draw Tickets based on points</strong></p>
+                    <p><strong>@Lang.GuideDrawTicketsBasedOnPoints</strong></p>
                     <div class="guide-table-wrap">
                         <table class="guide-table is-reward">
                             <thead>
-                                <tr><th>Total score month</th><th>Number of ticket</th></tr>
+                                <tr><th>@Lang.GuideTotalScoreMonth</th><th>@Lang.GuideNumberOfTicket</th></tr>
                             </thead>
                             <tbody>
-                                <tr><td>5,000 - 5,999</td><td>1 ticket</td></tr>
-                                <tr><td>6,000 - 6,999</td><td>2 tickets</td></tr>
-                                <tr><td>7,000 - 7,999</td><td>3 tickets</td></tr>
-                                <tr><td>8,000 - 8,999</td><td>5 tickets</td></tr>
-                                <tr><td>9,000 - 9,999</td><td>7 tickets</td></tr>
-                                <tr><td>&ge; 10,000</td><td>10 tickets</td></tr>
+                                <tr><td>5,000 - 5,999</td><td>@Lang.GuideOneTicket</td></tr>
+                                <tr><td>6,000 - 6,999</td><td>@Lang.GuideTwoTickets</td></tr>
+                                <tr><td>7,000 - 7,999</td><td>@Lang.GuideThreeTickets</td></tr>
+                                <tr><td>8,000 - 8,999</td><td>@Lang.GuideFiveTickets</td></tr>
+                                <tr><td>9,000 - 9,999</td><td>@Lang.GuideSevenTickets</td></tr>
+                                <tr><td>&ge; 10,000</td><td>@Lang.GuideTenTickets</td></tr>
                             </tbody>
                         </table>
                     </div>
-                    <p class="guide-note">On the 1st of each month at 5:00 AM, the system will announce the lucky customers who win the prizes.</p>
-                    <p class="guide-note">Each subscriber is eligible to receive a monthly prize only once within the most recent 3-month period.</p>
+                    <p class="guide-note">@Lang.GuideMonthlyAnnouncement</p>
+                    <p class="guide-note">@Lang.GuideMonthlyPrizeLimit</p>
                 </section>
             </div>
         </article>

+ 4 - 4
Kitty_Fall/Kitty_Fall/Kitty_Fall.Woker/Kitty_Fall.Woker.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
     <TargetFramework>net7.0</TargetFramework>
@@ -7,17 +7,17 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <Content Include="appsettings.Development.json">
+    <Content Update="appsettings.Development.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
       <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
     </Content>
-    <Content Include="appsettings.json">
+    <Content Update="appsettings.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
       <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
     </Content>
-    <Content Include="log4net.config">
+    <Content Update="log4net.config">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
       <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>

+ 4 - 1
Kitty_Fall/Kitty_Fall/Kitty_Fall.Woker/Program.cs

@@ -1,4 +1,5 @@
-using Database.Database;
+using Database.Database;
+using Kitty_Fall.Woker.Services;
 using log4net;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.EntityFrameworkCore;
@@ -29,6 +30,8 @@ if (!string.IsNullOrWhiteSpace(oracleSessionTimeZone))
 builder.Services.AddDbContext<ModelContext>(options =>
     options.UseOracle(builder.Configuration["Connection"]));
 
+builder.Services.AddHostedService<GameRewardPayoutWorker>();
+
 
 builder.Services.AddEndpointsApiExplorer();
 

+ 383 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Woker/Services/GameRewardPayoutWorker.cs

@@ -0,0 +1,383 @@
+using Common;
+using Database.Database;
+using Kitty_FallVasWs;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.EntityFrameworkCore;
+using System.Globalization;
+using System.ServiceModel;
+
+namespace Kitty_Fall.Woker.Services
+{
+    public class GameRewardPayoutWorker : BackgroundService
+    {
+        private const byte StatusNew = 0;
+        private const byte StatusProcessing = 1;
+        private const byte StatusSuccess = 2;
+        private const byte StatusFailed = 3;
+
+        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(GameRewardPayoutWorker));
+
+        private readonly IServiceScopeFactory scopeFactory;
+        private readonly IConfiguration configuration;
+
+        public GameRewardPayoutWorker(IServiceScopeFactory scopeFactory, IConfiguration configuration)
+        {
+            this.scopeFactory = scopeFactory;
+            this.configuration = configuration;
+        }
+
+        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            var intervalSeconds = GetInt("RewardWorker:IntervalSeconds", 10);
+            log.Info($"GameRewardPayoutWorker started. intervalSeconds={intervalSeconds}");
+
+            while (!stoppingToken.IsCancellationRequested)
+            {
+                try
+                {
+                    await ProcessPendingRewardsAsync(stoppingToken);
+                }
+                catch (Exception ex)
+                {
+                    log.Error("GameRewardPayoutWorker loop error", ex);
+                }
+
+                await Task.Delay(TimeSpan.FromSeconds(intervalSeconds), stoppingToken);
+            }
+        }
+
+        private async Task ProcessPendingRewardsAsync(CancellationToken cancellationToken)
+        {
+            using var scope = scopeFactory.CreateScope();
+            var dbContext = scope.ServiceProvider.GetRequiredService<ModelContext>();
+
+            var batchSize = GetInt("RewardWorker:BatchSize", 20);
+            var maxRetry = GetInt("RewardWorker:MaxRetry", 3);
+            var retryAfterMinutes = GetInt("RewardWorker:RetryAfterMinutes", 1);
+            var retryBefore = DateTime.Now.AddMinutes(-retryAfterMinutes);
+
+            // Lay cac log thuong can tra qua WS. SCORE la diem noi bo nen API da xu ly.
+            var rewardLogs = await dbContext.GameRewardLogs
+                .Where(x => (x.Status == StatusNew || x.Status == StatusFailed)
+                    && x.RetryCount < maxRetry
+                    && (x.RewardType == "DATA" || x.RewardType == "LOYALTY")
+                    && (x.UpdatedTime == null || x.UpdatedTime <= retryBefore))
+                .OrderBy(x => x.CreatedTime)
+                .ThenBy(x => x.RewardLogId)
+                .Take(batchSize)
+                .ToListAsync(cancellationToken);
+
+            foreach (var rewardLog in rewardLogs)
+            {
+                if (cancellationToken.IsCancellationRequested) return;
+                await ProcessRewardLogAsync(dbContext, rewardLog, maxRetry, cancellationToken);
+            }
+        }
+
+        private async Task ProcessRewardLogAsync(
+            ModelContext dbContext,
+            GameRewardLog rewardLog,
+            int maxRetry,
+            CancellationToken cancellationToken)
+        {
+            var now = DateTime.Now;
+
+            // Khoa mem log de tranh 2 vong worker cung tra mot reward.
+            rewardLog.Status = StatusProcessing;
+            rewardLog.RequestTime = now;
+            rewardLog.UpdatedTime = now;
+            await dbContext.SaveChangesAsync(cancellationToken);
+
+            try
+            {
+                var limitResult = await ApplyLimitBeforePayoutAsync(dbContext, rewardLog, maxRetry, now, cancellationToken);
+                if (!limitResult.CanPayout)
+                {
+                    await dbContext.SaveChangesAsync(cancellationToken);
+                    return;
+                }
+
+                if (!IsExternalReward(rewardLog.RewardType))
+                {
+                    // Fallback khong can goi WS thi danh dau xong de khong retry vo nghia.
+                    rewardLog.Status = StatusSuccess;
+                    rewardLog.ResponseTime = DateTime.Now;
+                    rewardLog.ResponseCode = "SUCCESS";
+                    rewardLog.ResponseMessage = "fallback reward completed";
+                    rewardLog.UpdatedTime = DateTime.Now;
+                    await dbContext.SaveChangesAsync(cancellationToken);
+                    return;
+                }
+
+                var wsResponse = await CallRewardWsAsync(rewardLog);
+                var response = wsResponse.Response;
+                var isSuccess = IsWsSuccess(response);
+
+                rewardLog.ResponseTime = DateTime.Now;
+                rewardLog.ResponseCode = response?.errorCode ?? response?.resultCode ?? (isSuccess ? "0" : "-1");
+                rewardLog.ResponseMessage = Truncate(response?.content ?? response?.status ?? wsResponse.RawMessage, 500);
+                rewardLog.ExternalTransId = Truncate(response?.resultCode ?? rewardLog.ExternalTransId ?? rewardLog.ChildTransId, 100);
+                rewardLog.UpdatedTime = DateTime.Now;
+
+                if (isSuccess)
+                {
+                    rewardLog.Status = StatusSuccess;
+                    await UpdateLimitUsageAsync(dbContext, rewardLog, now, cancellationToken);
+                }
+                else
+                {
+                    rewardLog.RetryCount++;
+                    rewardLog.Status = rewardLog.RetryCount >= maxRetry ? StatusFailed : StatusNew;
+                }
+
+                await dbContext.SaveChangesAsync(cancellationToken);
+            }
+            catch (Exception ex)
+            {
+                log.Error($"Process reward log error. rewardLogId={rewardLog.RewardLogId}", ex);
+
+                rewardLog.RetryCount++;
+                rewardLog.Status = rewardLog.RetryCount >= maxRetry ? StatusFailed : StatusNew;
+                rewardLog.ResponseTime = DateTime.Now;
+                rewardLog.ResponseCode = "EXCEPTION";
+                rewardLog.ResponseMessage = Truncate(ex.Message, 500);
+                rewardLog.UpdatedTime = DateTime.Now;
+                await dbContext.SaveChangesAsync(cancellationToken);
+            }
+        }
+
+        private async Task<LimitCheckResult> ApplyLimitBeforePayoutAsync(
+            ModelContext dbContext,
+            GameRewardLog rewardLog,
+            int maxRetry,
+            DateTime now,
+            CancellationToken cancellationToken)
+        {
+            // Kiem tra han muc neu DB co cau hinh, khong co cau hinh thi tra thuong binh thuong.
+            var limitConfigs = await dbContext.GameRewardLimitConfigs
+                .Where(x => x.Status != false
+                    && x.RewardType == rewardLog.RewardType
+                    && x.RewardUnit == rewardLog.RewardUnit
+                    && (x.ModeId == null || x.ModeId == rewardLog.ModeId)
+                    && (x.LevelNo == null || x.LevelNo == rewardLog.LevelNo)
+                    && (x.EffectiveFrom == null || x.EffectiveFrom <= now)
+                    && (x.EffectiveTo == null || x.EffectiveTo >= now))
+                .OrderByDescending(x => x.Priority)
+                .ThenBy(x => x.LimitConfigId)
+                .ToListAsync(cancellationToken);
+
+            foreach (var limitConfig in limitConfigs)
+            {
+                var periodKey = GetPeriodKey(limitConfig.PeriodType, now);
+                var usage = await dbContext.GameRewardLimitUsages
+                    .FirstOrDefaultAsync(x => x.LimitConfigId == limitConfig.LimitConfigId
+                        && x.PeriodKey == periodKey
+                        && x.RewardType == rewardLog.RewardType
+                        && x.RewardUnit == rewardLog.RewardUnit, cancellationToken);
+
+                var usedValue = usage?.UsedTotalValue ?? 0;
+                var usedCount = usage?.UsedTotalCount ?? 0;
+                var exceedValue = limitConfig.MaxTotalValue != null
+                    && usedValue + rewardLog.RewardValue > limitConfig.MaxTotalValue.Value;
+                var exceedCount = limitConfig.MaxTotalCount != null
+                    && usedCount + 1 > limitConfig.MaxTotalCount.Value;
+
+                if (!exceedValue && !exceedCount)
+                {
+                    rewardLog.LimitConfigId = limitConfig.LimitConfigId;
+                    return new LimitCheckResult(true);
+                }
+
+                var action = (limitConfig.ExceedAction ?? "").Trim().ToUpperInvariant();
+                if (action == "FALLBACK"
+                    && !string.IsNullOrWhiteSpace(limitConfig.FallbackRewardType)
+                    && limitConfig.FallbackRewardValue.GetValueOrDefault() > 0
+                    && !string.IsNullOrWhiteSpace(limitConfig.FallbackRewardUnit))
+                {
+                    // Doi sang reward fallback theo cau hinh han muc.
+                    rewardLog.LimitConfigId = limitConfig.LimitConfigId;
+                    rewardLog.RewardType = limitConfig.FallbackRewardType!;
+                    rewardLog.RewardValue = limitConfig.FallbackRewardValue!.Value;
+                    rewardLog.RewardUnit = limitConfig.FallbackRewardUnit!;
+                    return new LimitCheckResult(true);
+                }
+
+                // Vuot han muc va khong co fallback thi danh dau failed, khong retry lap lai.
+                rewardLog.LimitConfigId = limitConfig.LimitConfigId;
+                rewardLog.Status = StatusFailed;
+                rewardLog.RetryCount = (short)maxRetry;
+                rewardLog.ResponseTime = now;
+                rewardLog.ResponseCode = "LIMIT_EXCEEDED";
+                rewardLog.ResponseMessage = $"Reward limit exceeded: {limitConfig.LimitCode}";
+                rewardLog.UpdatedTime = now;
+                return new LimitCheckResult(false);
+            }
+
+            return new LimitCheckResult(true);
+        }
+
+        private async Task UpdateLimitUsageAsync(
+            ModelContext dbContext,
+            GameRewardLog rewardLog,
+            DateTime now,
+            CancellationToken cancellationToken)
+        {
+            if (rewardLog.LimitConfigId == null) return;
+
+            var limitConfig = await dbContext.GameRewardLimitConfigs
+                .AsNoTracking()
+                .FirstOrDefaultAsync(x => x.LimitConfigId == rewardLog.LimitConfigId.Value, cancellationToken);
+            if (limitConfig == null) return;
+
+            var periodKey = GetPeriodKey(limitConfig.PeriodType, now);
+            var usage = await dbContext.GameRewardLimitUsages
+                .FirstOrDefaultAsync(x => x.LimitConfigId == rewardLog.LimitConfigId.Value
+                    && x.PeriodKey == periodKey
+                    && x.RewardType == rewardLog.RewardType
+                    && x.RewardUnit == rewardLog.RewardUnit, cancellationToken);
+
+            if (usage == null)
+            {
+                usage = new GameRewardLimitUsage
+                {
+                    LimitUsageId = await NextDecimalIdAsync(dbContext, "SEQ_GAME_REWARD_LIMIT_USAGE"),
+                    LimitConfigId = rewardLog.LimitConfigId.Value,
+                    PeriodType = limitConfig.PeriodType,
+                    PeriodKey = periodKey,
+                    RewardType = rewardLog.RewardType,
+                    RewardUnit = rewardLog.RewardUnit,
+                    UsedTotalValue = 0,
+                    UsedTotalCount = 0,
+                    CreatedTime = now,
+                    LastUpdateTime = now
+                };
+                dbContext.GameRewardLimitUsages.Add(usage);
+            }
+
+            usage.UsedTotalValue += rewardLog.RewardValue;
+            usage.UsedTotalCount += 1;
+            usage.LastRewardLogId = rewardLog.RewardLogId;
+            usage.LastUpdateTime = now;
+            usage.UpdatedTime = now;
+        }
+
+        private async Task<RewardWsResult> CallRewardWsAsync(GameRewardLog rewardLog)
+        {
+            var wsUrl = GetString("wsUrl");
+            using var wsClient = new KittyFallWsClient();
+            if (!string.IsNullOrWhiteSpace(wsUrl))
+            {
+                wsClient.Endpoint.Address = new EndpointAddress(wsUrl);
+            }
+
+            var wsUser = GetString("wsUser");
+            var wsPass = GetString("wsPass");
+            var serviceId = GetString("RewardWorker:ServiceId", GetString("RewardWorker:DefaultServiceId", "KittyFall"));
+            var packageName = GetPackageName(rewardLog);
+            var valueText = FormatRewardValue(rewardLog.RewardValue);
+
+            // Goi dung WS theo loai thuong can cong cho user.
+            if (string.Equals(rewardLog.RewardType, "DATA", StringComparison.OrdinalIgnoreCase))
+            {
+                var response = await wsClient.wsAddDataAsync(
+                    wsUser,
+                    wsPass,
+                    rewardLog.Msisdn,
+                    packageName,
+                    valueText,
+                    serviceId);
+
+                return new RewardWsResult(response.@return, $"wsAddData rewardLogId={rewardLog.RewardLogId}");
+            }
+
+            if (string.Equals(rewardLog.RewardType, "LOYALTY", StringComparison.OrdinalIgnoreCase))
+            {
+                var response = await wsClient.wsAddPointAsync(
+                    wsUser,
+                    wsPass,
+                    rewardLog.Msisdn,
+                    packageName,
+                    valueText,
+                    serviceId);
+
+                return new RewardWsResult(response.@return, $"wsAddPoint rewardLogId={rewardLog.RewardLogId}");
+            }
+
+            return new RewardWsResult(null, $"Unsupported reward type {rewardLog.RewardType}");
+        }
+
+        private static bool IsExternalReward(string? rewardType)
+        {
+            return string.Equals(rewardType, "DATA", StringComparison.OrdinalIgnoreCase)
+                || string.Equals(rewardType, "LOYALTY", StringComparison.OrdinalIgnoreCase);
+        }
+
+        private string GetPackageName(GameRewardLog rewardLog)
+        {
+            if (string.Equals(rewardLog.RewardType, "DATA", StringComparison.OrdinalIgnoreCase))
+            {
+                return GetString("RewardWorker:DataPackageName", $"GAME_DATA_{FormatRewardValue(rewardLog.RewardValue)}{rewardLog.RewardUnit}");
+            }
+
+            if (string.Equals(rewardLog.RewardType, "LOYALTY", StringComparison.OrdinalIgnoreCase))
+            {
+                return GetString("RewardWorker:LoyaltyPackageName", "GAME_LOYALTY");
+            }
+
+            return GetString("RewardWorker:DefaultPackageName", "GAME_REWARD");
+        }
+
+        private static bool IsWsSuccess(response? response)
+        {
+            return response?.errorCode == CommonErrorCode.Success
+                || response?.errorCode == CommonErrorCode.SuccessFirstRegister
+                || response?.resultCode == CommonErrorCode.Success
+                || string.Equals(response?.status, "SUCCESS", StringComparison.OrdinalIgnoreCase)
+                || string.Equals(response?.status, "TRUE", StringComparison.OrdinalIgnoreCase);
+        }
+
+        private static int GetPeriodKey(string? periodType, DateTime now)
+        {
+            return string.Equals(periodType, "MONTHLY", StringComparison.OrdinalIgnoreCase)
+                ? now.Year * 100 + now.Month
+                : now.Year * 10000 + now.Month * 100 + now.Day;
+        }
+
+        private static string FormatRewardValue(decimal value)
+        {
+            return value % 1 == 0
+                ? value.ToString("0", CultureInfo.InvariantCulture)
+                : value.ToString("0.##", CultureInfo.InvariantCulture);
+        }
+
+        private static async Task<decimal> NextDecimalIdAsync(ModelContext dbContext, string sequence)
+        {
+            return await Database.DbLogic.GenIdAsync(dbContext, sequence)
+                ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+        }
+
+        private int GetInt(string key, int defaultValue)
+        {
+            var value = configuration[key];
+            return int.TryParse(value, out var result) && result > 0 ? result : defaultValue;
+        }
+
+        private string GetString(string key, string defaultValue = "")
+        {
+            var value = configuration[key];
+            return string.IsNullOrWhiteSpace(value) ? defaultValue : value;
+        }
+
+        private static string? Truncate(string? value, int maxLength)
+        {
+            if (string.IsNullOrWhiteSpace(value)) return value;
+            return value.Length <= maxLength ? value : value[..maxLength];
+        }
+
+        private readonly record struct LimitCheckResult(bool CanPayout);
+
+        private readonly record struct RewardWsResult(response? Response, string RawMessage);
+    }
+}

+ 14 - 4
Kitty_Fall/Kitty_Fall/Kitty_Fall.Woker/appsettings.Development.json

@@ -1,11 +1,21 @@
 {
   //"Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=myjob;Password=HHwx4ISVN51cDam;Connection Timeout=120;",
-  "Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1539))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=myjob;Password=123456;Connection Timeout=120;",
+  "Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1539))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=Kitty_Fall;Password=123456;Connection Timeout=120;",
   "OracleSessionTimeZone": "UTC",
 
-  "wsUrl": "http://127.0.0.1:8199/MyJobWs?wsdl",
-  "wsUser": "ws_myjob",
-  "wsPass": "Myjob@123A",
+  "wsUrl": "http://127.0.0.1:8199/KittyFallWs?wsdl",
+  "wsUser": "ws_KittyFall",
+  "wsPass": "KittyFall@123A",
+  "RewardWorker": {
+    "IntervalSeconds": 10,
+    "BatchSize": 20,
+    "MaxRetry": 3,
+    "RetryAfterMinutes": 1,
+    "ServiceId": "KittyFall",
+    "DataPackageName": "GAME_DATA",
+    "LoyaltyPackageName": "GAME_LOYALTY",
+    "DefaultPackageName": "GAME_REWARD"
+  },
   "Logging": {
     "LogLevel": {
       "Default": "Information",

+ 14 - 4
Kitty_Fall/Kitty_Fall/Kitty_Fall.Woker/appsettings.json

@@ -1,11 +1,21 @@
 {
   //"Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=myjob;Password=HHwx4ISVN51cDam;Connection Timeout=120;",
-  "Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1539))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=myjob;Password=123456;Connection Timeout=120;",
+  "Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1539))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=Kitty_Fall;Password=123456;Connection Timeout=120;",
   "OracleSessionTimeZone": "UTC",
   
-  "wsUrl": "http://127.0.0.1:8199/MyJobWs?wsdl",
-  "wsUser": "ws_myjob",
-  "wsPass": "Myjob@123A",
+  "wsUrl": "http://127.0.0.1:8199/KittyFallWs?wsdl",
+  "wsUser": "ws_KittyFall",
+  "wsPass": "KittyFall@123A",
+  "RewardWorker": {
+    "IntervalSeconds": 10,
+    "BatchSize": 20,
+    "MaxRetry": 3,
+    "RetryAfterMinutes": 1,
+    "ServiceId": "KittyFall",
+    "DataPackageName": "GAME_DATA",
+    "LoyaltyPackageName": "GAME_LOYALTY",
+    "DefaultPackageName": "GAME_REWARD"
+  },
   "Logging": {
     "LogLevel": {
       "Default": "Information",