Ver Fonte

thêm luồng thanh toán qua ví khi mua trên website gọi qua anh Quang

student há 1 mês atrás
pai
commit
a898c0dd49

BIN
API NATCOM LOTTERY.docx


+ 140 - 0
NATCOM_LOTO_WALLET.postman_collection.json

@@ -0,0 +1,140 @@
+{
+  "info": {
+    "_postman_id": "848020d3-bd8b-4f64-bb10-aa7f78073aea",
+    "name": "NATCOM_LOTO_WALLET",
+    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
+    "_exporter_id": "19302593"
+  },
+  "item": [
+    {
+      "name": "buyTicket Pro",
+      "request": {
+        "method": "POST",
+        "header": [],
+        "body": {
+          "mode": "raw",
+          "raw": "{\"requestId\":\"123456789\",\"channel\":\"USSD\",\"channelPayment\":\"WALLET\",\"walletPin\":\"123456\",\"msisdn\":\"50935949762\",\"language\":\"0\",\"gameId\":\"30\",\"promotionCode\":\"12345\",\"token\":\"Q5VJq6IQ1O4OuT+y0RkfF9T/FK0FFrRlVHSkaHOPJywlqiqmNs1P5qClOTxdstKEMVtIVhUrGTsxDx41uDHoZvYz3YmINJtj9p+g58ehKVo=\",\r\n\"ticket\": [{\r\n            \"code\": \"03,10,17,21,23,24,28,29,63,70\",\r\n            \"money\": \"50\"\r\n        },\r\n        {\r\n            \"code\": \"03,10,17,21,23,24,28,29,63,70\",\r\n            \"money\": \"50\"\r\n        }\r\n    ]\r\n\r\n}\r\n",
+          "options": {
+            "raw": {
+              "language": "json"
+            }
+          }
+        },
+        "url": {
+          "raw": "http://localhost:41341/api/loto/buyTicketV2/data",
+          "protocol": "http",
+          "host": [
+            "localhost"
+          ],
+          "port": "41341",
+          "path": [
+            "api",
+            "loto",
+            "buyTicketV2",
+            "data"
+          ]
+        }
+      },
+      "response": []
+    },
+    {
+      "name": "buyTicketConfirmPro",
+      "request": {
+        "method": "POST",
+        "header": [],
+        "body": {
+          "mode": "raw",
+          "raw": "{\"requestId\":\"12345\",\"transIdByTicket\":\"5bc0f27b-2a5f-4d35-ab77-6f2de1044440\",\"paymentCode\":\"USSD\",\"channel\":\"USSD\",\"token\":\"Q5VJq6IQ1O4OuT+y0RkfF9T/FK0FFrRlVHSkaHOPJywlqiqmNs1P5qClOTxdstKEMVtIVhUrGTsxDx41uDHoZvYz3YmINJtj9p+g58ehKVo=\"}",
+          "options": {
+            "raw": {
+              "language": "json"
+            }
+          }
+        },
+        "url": {
+          "raw": "http://171.244.50.177:8900/api/loto/confirmTicketV2/data",
+          "protocol": "http",
+          "host": [
+            "171",
+            "244",
+            "50",
+            "177"
+          ],
+          "port": "8900",
+          "path": [
+            "api",
+            "loto",
+            "confirmTicketV2",
+            "data"
+          ]
+        }
+      },
+      "response": []
+    },
+    {
+      "name": "getRandomPromotion",
+      "request": {
+        "method": "POST",
+        "header": [],
+        "body": {
+          "mode": "raw",
+          "raw": "{\"requestId\":\"12345\",\"gameId\":\"30\",\"msisdn\":\"509123456\",\"channel\":\"USSD\",\"language\":\"1\",\"token\":\"Q5VJq6IQ1O4OuT+y0RkfF9T/FK0FFrRlVHSkaHOPJywlqiqmNs1P5qClOTxdstKEMVtIVhUrGTsxDx41uDHoZvYz3YmINJtj9p+g58ehKVo=\"}",
+          "options": {
+            "raw": {
+              "language": "json"
+            }
+          }
+        },
+        "url": {
+          "raw": "http://localhost:41341/api/loto/getPromotionCode/data",
+          "protocol": "http",
+          "host": [
+            "localhost"
+          ],
+          "port": "41341",
+          "path": [
+            "api",
+            "loto",
+            "getPromotionCode",
+            "data"
+          ]
+        }
+      },
+      "response": []
+    },
+    {
+      "name": "getJackport",
+      "request": {
+        "method": "POST",
+        "header": [],
+        "body": {
+          "mode": "raw",
+          "raw": "{\"gameId\":\"33\",\"channel\":\"USSD\",\"language\":\"1\",\"token\":\"Q5VJq6IQ1O4OuT+y0RkfF9T/FK0FFrRlVHSkaHOPJywlqiqmNs1P5qClOTxdstKEMVtIVhUrGTsxDx41uDHoZvYz3YmINJtj9p+g58ehKVo=\"}",
+          "options": {
+            "raw": {
+              "language": "json"
+            }
+          }
+        },
+        "url": {
+          "raw": "http://171.244.50.177:8900/api/loto/getJackportMilinons/data",
+          "protocol": "http",
+          "host": [
+            "171",
+            "244",
+            "50",
+            "177"
+          ],
+          "port": "8900",
+          "path": [
+            "api",
+            "loto",
+            "getJackportMilinons",
+            "data"
+          ]
+        }
+      },
+      "response": []
+    }
+  ]
+}

+ 22 - 18
website/Areas/LotteryV2/Controllers/HomeController.cs

@@ -701,7 +701,9 @@ namespace LotteryWebApp.Areas.LotteryV2.Controllers
                 request.language = CultureInfo.CurrentCulture.Name.StartsWith("en") ? "0" : "1";
                 request.requestId = Guid.NewGuid().ToString();
 
-                ConfirmTicketDataResponse response = api.ConfirmTicketDataApi(configuration, request);
+                ConfirmTicketDataResponse response = request.channelPayment == Constants.NATCASH_WALLET_TICKET
+                    ? api.BuyTicketV2Api(configuration, request)
+                    : api.ConfirmTicketDataApi(configuration, request);
                 log.Info($"[ConfirmTicketData] Call ConfirmTicketDataApi Response: {JsonConvert.SerializeObject(response)}");
 
                 return Json(response);
@@ -789,24 +791,24 @@ namespace LotteryWebApp.Areas.LotteryV2.Controllers
                     return Json(new { responseCode = Code.SESSION_EXPIRED, responseMessage = "Session expired" });
                 }
 
-                // Step 1: Verify OTP first
-                ConfirmOTPRequest otpRequest = new ConfirmOTPRequest
-                {
-                    otp = request.paymentCode,
-                    msisdn = msisdn,
-                    token = token,
-                    language = CultureInfo.CurrentCulture.Name.StartsWith("en") ? "0" : "1",
-                    channel = configuration.GetSection("channel").Value
-                };
-                log.Info($"[ConfirmBuyingTicketV2] Call ConfirmOTPApi Request: {JsonConvert.SerializeObject(otpRequest)}");
-
-                ConfirmOTPResponse otpResponse = api.ConfirmOTPApi(configuration, otpRequest);
-                log.Info($"[ConfirmBuyingTicketV2] Call ConfirmOTPApi Response: {JsonConvert.SerializeObject(otpResponse)}");
+                var isWalletPayment = request.channelPayment == Constants.NATCASH_WALLET_TICKET;
 
-                if (otpResponse.responseCode != Code.SUCCESS)
+                // The main-account flow keeps the existing OTP verification.
+                if (!isWalletPayment)
                 {
-                    log.Info($"[ConfirmBuyingTicketV2] OTP Verification Failed: {otpResponse.responseMessage}");
-                    return Json(new { responseCode = otpResponse.responseCode, responseMessage = otpResponse.responseMessage });
+                    ConfirmOTPRequest otpRequest = new ConfirmOTPRequest
+                    {
+                        otp = request.paymentCode,
+                        msisdn = msisdn,
+                        token = token,
+                        language = CultureInfo.CurrentCulture.Name.StartsWith("en") ? "0" : "1",
+                        channel = configuration.GetSection("channel").Value
+                    };
+                    ConfirmOTPResponse otpResponse = api.ConfirmOTPApi(configuration, otpRequest);
+                    if (otpResponse.responseCode != Code.SUCCESS)
+                    {
+                        return Json(new { responseCode = otpResponse.responseCode, responseMessage = otpResponse.responseMessage });
+                    }
                 }
 
                 // Step 2: Proceed to Confirm buying
@@ -818,7 +820,9 @@ namespace LotteryWebApp.Areas.LotteryV2.Controllers
 
                 log.Info($"[ConfirmBuyingTicketV2] Call ConfirmBuyingTicketApi Request: {JsonConvert.SerializeObject(request)}");
 
-                ConfirmBuyingTicketResponse response = api.ConfirmBuyingTicketApi(configuration, request);
+                ConfirmBuyingTicketResponse response = isWalletPayment
+                    ? api.ConfirmBuyingTicketV2Api(configuration, request, Constants.WALLET_PAYMENT_CHANNEL, request.walletPin)
+                    : api.ConfirmBuyingTicketApi(configuration, request);
                 log.Info($"[ConfirmBuyingTicketV2] Call ConfirmBuyingTicketApi Response: {JsonConvert.SerializeObject(response)}");
 
                 if (response.responseCode == Code.SUCCESS)

+ 69 - 26
website/Areas/LotteryV2/Views/Home/BuyTicket.cshtml

@@ -294,6 +294,36 @@ else
 </div>
 }
 
+<!-- Payment channel selection -->
+<div id="paymentChannelModal" class="hidden fixed inset-0 z-[140] bg-black/70 backdrop-blur-[3px] items-center justify-center p-4 font-bricolage">
+    <div class="relative w-full max-w-[370px] overflow-hidden rounded-[32px] bg-[#F8F9FC] shadow-[0_28px_70px_rgba(0,0,0,0.35)] animate__animated animate__zoomIn animate__faster">
+        <div class="relative overflow-hidden bg-gradient-to-br from-[#EE0033] to-[#B80028] px-6 pb-7 pt-6 text-white">
+            <div class="absolute -right-8 -top-10 h-32 w-32 rounded-full bg-white/10"></div>
+            <div class="absolute -bottom-12 -left-8 h-28 w-28 rounded-full bg-black/10"></div>
+            <button type="button" onclick="hidePaymentChannelModal()" class="absolute right-4 top-4 z-10 flex h-9 w-9 items-center justify-center rounded-full bg-white/15 text-white transition hover:bg-white/25 active:scale-90" aria-label="Close">
+                <i class="fa-solid fa-xmark"></i>
+            </button>
+            <div class="relative z-[1] flex items-center gap-4">
+                <div class="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-white text-[25px] text-[#EE0033] shadow-lg"><i class="fa-solid fa-wallet"></i></div>
+                <div><h2 class="text-[21px] font-black leading-tight">@Lang.payment_method_title</h2><p class="mt-1 text-[13px] font-semibold text-white/80">@Lang.payment_method_subtitle</p></div>
+            </div>
+        </div>
+        <div class="space-y-3 p-5">
+            <button type="button" onclick="selectPaymentChannel('@Constants.BASIC_WALLET_TICKET')" class="group flex w-full items-center gap-4 rounded-[20px] border-2 border-transparent bg-white p-4 text-left shadow-[0_5px_18px_rgba(31,41,55,0.07)] transition hover:-translate-y-0.5 hover:border-[#EE0033] active:scale-[0.98]">
+                <span class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-[#FFE8ED] text-[21px] text-[#EE0033]"><i class="fa-solid fa-mobile-screen-button"></i></span>
+                <span class="min-w-0 flex-1"><span class="block text-[16px] font-black text-[#171A24]">@Lang.payment_main_account</span><span class="mt-0.5 block text-[12px] font-semibold text-gray-500">@Lang.payment_main_account_desc</span></span>
+                <span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-400 transition group-hover:bg-[#EE0033] group-hover:text-white"><i class="fa-solid fa-chevron-right text-[12px]"></i></span>
+            </button>
+            <button type="button" onclick="selectPaymentChannel('@Constants.NATCASH_WALLET_TICKET')" class="group flex w-full items-center gap-4 rounded-[20px] border-2 border-transparent bg-white p-4 text-left shadow-[0_5px_18px_rgba(31,41,55,0.07)] transition hover:-translate-y-0.5 hover:border-[#0A9800] active:scale-[0.98]">
+                <span class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-[#E7F8E6] text-[21px] text-[#0A9800]"><i class="fa-solid fa-wallet"></i></span>
+                <span class="min-w-0 flex-1"><span class="flex items-center gap-2 text-[16px] font-black text-[#171A24]">@Lang.payment_wallet <span class="rounded-full bg-[#E7F8E6] px-2 py-0.5 text-[9px] font-black text-[#0A9800]">PIN</span></span><span class="mt-0.5 block text-[12px] font-semibold text-gray-500">@Lang.payment_wallet_desc</span></span>
+                <span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-400 transition group-hover:bg-[#0A9800] group-hover:text-white"><i class="fa-solid fa-chevron-right text-[12px]"></i></span>
+            </button>
+            <p class="pt-1 text-center text-[11px] font-semibold text-gray-400"><i class="fa-solid fa-shield-halved mr-1 text-[#0A9800]"></i>@Lang.payment_security_message</p>
+        </div>
+    </div>
+</div>
+
 <!-- Order Summary Screen (from Figma) -->
 <div id="orderSummaryModal" class="hidden fixed inset-0 z-[150] bg-black/60 flex items-center justify-center p-0 font-bricolage backdrop-blur-[2px]">
     <div class="w-full h-full bg-white flex flex-col animate__animated animate__fadeInRight animate__faster shadow-2xl relative lg:max-w-[414px]">
@@ -429,7 +459,7 @@ else
                         </div>
                     </div>
         
-                    <p class="text-[14px] font-bold text-[#555] text-center px-4 leading-snug mt-0 font-bricolage">@Lang.v2_otp_instruction</p>
+                    <p id="paymentCodeInstruction" class="text-[14px] font-bold text-[#555] text-center px-4 leading-snug mt-0 font-bricolage">@Lang.v2_otp_instruction</p>
         
                     <!-- 6-Slots OTP Grid (Large Gray Blocks) -->
                     <div class="flex items-center justify-center gap-2 w-full mt-1 px-2" id="otpInputs">
@@ -824,6 +854,9 @@ else
 
         // --- Order Summary Modal Logic ---
         // --- Payment Preparation & Order Summary Logic ---
+        let pendingTickets = [];
+        let currentChannelPayment = '@Constants.BASIC_WALLET_TICKET';
+
         function preparePayment(event) {
             const isBSMode = @(Model.termType == Constants.PIC10_BIGSMALL_CODE || Model.termType == Constants.PIC10_ODDEVEN_CODE ? "true" : "false");
             const tickets = [];
@@ -865,19 +898,24 @@ else
                 }
             }
 
+            pendingTickets = tickets;
+            $("#paymentChannelModal").removeClass("hidden").addClass("flex");
+        }
+
+        function hidePaymentChannelModal() {
+            $("#paymentChannelModal").removeClass("flex").addClass("hidden");
+        }
+
+        function selectPaymentChannel(channelPayment) {
+            currentChannelPayment = channelPayment;
+            hidePaymentChannelModal();
+
             const requestData = {
                 gameId: "@Model.termType",
-                ticket: tickets
+                ticket: pendingTickets,
+                channelPayment: channelPayment
             };
 
-            const btn = event ? event.currentTarget : null;
-            const originalText = btn ? btn.innerHTML : "@Lang.v2_payment";
-            if (btn) {
-                btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Loading...';
-                btn.disabled = true;
-            }
-
-
             $.ajax({
                 url: subDomain + '@Url.Action("ConfirmTicketData", "Home")',
                 type: 'POST',
@@ -885,23 +923,14 @@ else
                 data: JSON.stringify(requestData),
                 success: function(data) {
 
-                    if (btn) {
-                        btn.innerHTML = originalText;
-                        btn.disabled = false;
-                    }
-                    if (data.responseCode === "0") {
-                        showOrderSummary(data, tickets);
+                    if (data.responseCode === "0" || data.responseCode === "0000") {
+                        showOrderSummary(data, pendingTickets);
                     } else {
                         showNotification(data.responseMessage || "Confirmation failed", data.responseCode);
                     }
                 },
                 error: function(err) {
 
-                    if (btn) {
-                        btn.innerHTML = originalText;
-                        btn.disabled = false;
-                    }
-
                     showNotification("Network error occurred.", "warning");
                 }
             });
@@ -968,6 +997,11 @@ else
         }
 
         function confirmCheckout(btn) {
+            if (currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET') {
+                showOtpModal(true);
+                return;
+            }
+
             const originalText = $(btn).html();
             $(btn).prop('disabled', true).html('<i class="fa-solid fa-spinner fa-spin mr-2"></i>Processing...');
 
@@ -979,7 +1013,7 @@ else
                     $(btn).prop('disabled', false).html(originalText);
                     
                     if (data.responseCode === "0") {
-                        showOtpModal();
+                        showOtpModal(false);
                     } else {
                         showNotification(data.responseMessage || "Failed to send OTP", data.responseCode);
                     }
@@ -992,7 +1026,7 @@ else
         }
 
         let otpInterval;
-        function showOtpModal() {
+        function showOtpModal(isWalletPin) {
             hideOrderSummary();
             $("#otpModal").removeClass("hidden").addClass("flex");
             $("#otpInputs input").val("");
@@ -1034,7 +1068,14 @@ else
             $("#otpTicketTypeContainer").append(`<span class='text-[17px] font-[900] ${typeColorClass} italic'>${displayName}</span>`);
             
             $("#otpTotalAmount").text($("#summaryTotalAmount").text());
-            startOtpTimer(60);
+            if (isWalletPin) {
+                $("#paymentCodeInstruction").text('@Html.Raw(Lang.payment_wallet_pin_instruction.Replace("'", "\\'"))');
+                $("#otpTimer, #resendOtpBtn").addClass("hidden");
+                clearInterval(otpInterval);
+            } else {
+                $("#paymentCodeInstruction").text('@Html.Raw(Lang.v2_otp_instruction.Replace("'", "\\'"))');
+                startOtpTimer(60);
+            }
         }
 
         // Add backspace support for OTP inputs
@@ -1140,7 +1181,7 @@ else
             });
             
             if (otpCode.length < 6) {
-                showNotification("Please enter full 6-digit OTP", "warning");
+                showNotification(currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET' ? "Please enter full 6-digit PIN" : "Please enter full 6-digit OTP", "warning");
                 return;
             }
 
@@ -1149,7 +1190,9 @@ else
 
             const finalData = {
                 transIdByTicket: currentTransId,
-                paymentCode: otpCode
+                paymentCode: currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET' ? null : otpCode,
+                walletPin: currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET' ? otpCode : null,
+                channelPayment: currentChannelPayment
             };
 
             // Call the unified backend action that verifies OTP and then buys the ticket

+ 22 - 14
website/Areas/Millions/Controllers/HomeController.cs

@@ -997,7 +997,9 @@ namespace LotteryWebApp.Areas.Millions.Controllers
                 var ticketDebug = request.ticket != null ? string.Join("; ", request.ticket.Select(t => $"code={t.code}, money={t.money}")) : "NULL";
                 log.Info($"[ConfirmTicketData] gameId={request.gameId}, msisdn={request.msisdn}, tickets=[{ticketDebug}]");
 
-                ConfirmTicketDataResponse response = api.ConfirmTicketDataApi(configuration, request);
+                ConfirmTicketDataResponse response = request.channelPayment == Constants.NATCASH_WALLET_TICKET
+                    ? api.BuyTicketV2Api(configuration, request)
+                    : api.ConfirmTicketDataApi(configuration, request);
                 
                 // DEBUG: Log response
                 log.Info($"[ConfirmTicketData] Response: code={response.responseCode}, msg={response.responseMessage}, transId={response.transId}");
@@ -1069,20 +1071,24 @@ namespace LotteryWebApp.Areas.Millions.Controllers
                 var msisdn = HttpContext.Session.GetComplexData<string>("msisdn");
                 if (string.IsNullOrEmpty(token)) return Json(new { responseCode = Code.SESSION_EXPIRED, responseMessage = "Session expired" });
 
-                // Step 1: Verify OTP first
-                ConfirmOTPRequest otpRequest = new ConfirmOTPRequest
-                {
-                    otp = request.paymentCode,
-                    msisdn = msisdn,
-                    token = token,
-                    language = CultureInfo.CurrentCulture.Name.StartsWith("en") ? "0" : "1",
-                    channel = configuration.GetSection("channel").Value
-                };
-                ConfirmOTPResponse otpResponse = api.ConfirmOTPApi(configuration, otpRequest);
+                var isWalletPayment = request.channelPayment == Constants.NATCASH_WALLET_TICKET;
 
-                if (otpResponse.responseCode != Code.SUCCESS)
+                // The main-account flow keeps the existing OTP verification.
+                if (!isWalletPayment)
                 {
-                    return Json(new { responseCode = otpResponse.responseCode, responseMessage = otpResponse.responseMessage });
+                    ConfirmOTPRequest otpRequest = new ConfirmOTPRequest
+                    {
+                        otp = request.paymentCode,
+                        msisdn = msisdn,
+                        token = token,
+                        language = CultureInfo.CurrentCulture.Name.StartsWith("en") ? "0" : "1",
+                        channel = configuration.GetSection("channel").Value
+                    };
+                    ConfirmOTPResponse otpResponse = api.ConfirmOTPApi(configuration, otpRequest);
+                    if (otpResponse.responseCode != Code.SUCCESS)
+                    {
+                        return Json(new { responseCode = otpResponse.responseCode, responseMessage = otpResponse.responseMessage });
+                    }
                 }
 
                 // Step 2: Proceed to Confirm buying
@@ -1092,7 +1098,9 @@ namespace LotteryWebApp.Areas.Millions.Controllers
                 request.language = CultureInfo.CurrentCulture.Name.StartsWith("en") ? "0" : "1";
                 request.channel = configuration.GetSection("channel").Value;
 
-                ConfirmBuyingTicketResponse response = api.ConfirmBuyingTicketApi(configuration, request);
+                ConfirmBuyingTicketResponse response = isWalletPayment
+                    ? api.ConfirmBuyingTicketV2Api(configuration, request, Constants.WALLET_PAYMENT_CHANNEL, request.walletPin)
+                    : api.ConfirmBuyingTicketApi(configuration, request);
                 if (response.responseCode == Code.SUCCESS)
                 {
                     UpdateUserStatus(msisdn, token);

+ 59 - 26
website/Areas/Millions/Views/Home/BuyTicket.cshtml

@@ -311,6 +311,26 @@ else
 </div>
 }
 
+<!-- Payment channel selection -->
+<div id="paymentChannelModal" class="hidden fixed inset-0 z-[140] bg-black/70 backdrop-blur-[3px] items-center justify-center p-4 font-bricolage">
+    <div class="relative w-full max-w-[370px] overflow-hidden rounded-[32px] bg-[#F8F9FC] shadow-[0_28px_70px_rgba(0,0,0,0.35)] animate__animated animate__zoomIn animate__faster">
+        <div class="relative overflow-hidden bg-gradient-to-br from-[#0062FF] to-[#003FA8] px-6 pb-7 pt-6 text-white">
+            <div class="absolute -right-8 -top-10 h-32 w-32 rounded-full bg-white/10"></div><div class="absolute -bottom-12 -left-8 h-28 w-28 rounded-full bg-black/10"></div>
+            <button type="button" onclick="hidePaymentChannelModal()" class="absolute right-4 top-4 z-10 flex h-9 w-9 items-center justify-center rounded-full bg-white/15 text-white transition hover:bg-white/25 active:scale-90" aria-label="Close"><i class="fa-solid fa-xmark"></i></button>
+            <div class="relative z-[1] flex items-center gap-4"><div class="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-white text-[25px] text-[#0062FF] shadow-lg"><i class="fa-solid fa-wallet"></i></div><div><h2 class="text-[21px] font-black leading-tight">@Lang.payment_method_title</h2><p class="mt-1 text-[13px] font-semibold text-white/80">@Lang.payment_method_subtitle</p></div></div>
+        </div>
+        <div class="space-y-3 p-5">
+            <button type="button" onclick="selectPaymentChannel('@Constants.BASIC_WALLET_TICKET')" class="group flex w-full items-center gap-4 rounded-[20px] border-2 border-transparent bg-white p-4 text-left shadow-[0_5px_18px_rgba(31,41,55,0.07)] transition hover:-translate-y-0.5 hover:border-[#0062FF] active:scale-[0.98]">
+                <span class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-[#E7F0FF] text-[21px] text-[#0062FF]"><i class="fa-solid fa-mobile-screen-button"></i></span><span class="min-w-0 flex-1"><span class="block text-[16px] font-black text-[#171A24]">@Lang.payment_main_account</span><span class="mt-0.5 block text-[12px] font-semibold text-gray-500">@Lang.payment_main_account_desc</span></span><span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-400 transition group-hover:bg-[#0062FF] group-hover:text-white"><i class="fa-solid fa-chevron-right text-[12px]"></i></span>
+            </button>
+            <button type="button" onclick="selectPaymentChannel('@Constants.NATCASH_WALLET_TICKET')" class="group flex w-full items-center gap-4 rounded-[20px] border-2 border-transparent bg-white p-4 text-left shadow-[0_5px_18px_rgba(31,41,55,0.07)] transition hover:-translate-y-0.5 hover:border-[#0A9800] active:scale-[0.98]">
+                <span class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-[#E7F8E6] text-[21px] text-[#0A9800]"><i class="fa-solid fa-wallet"></i></span><span class="min-w-0 flex-1"><span class="flex items-center gap-2 text-[16px] font-black text-[#171A24]">@Lang.payment_wallet <span class="rounded-full bg-[#E7F8E6] px-2 py-0.5 text-[9px] font-black text-[#0A9800]">PIN</span></span><span class="mt-0.5 block text-[12px] font-semibold text-gray-500">@Lang.payment_wallet_desc</span></span><span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-400 transition group-hover:bg-[#0A9800] group-hover:text-white"><i class="fa-solid fa-chevron-right text-[12px]"></i></span>
+            </button>
+            <p class="pt-1 text-center text-[11px] font-semibold text-gray-400"><i class="fa-solid fa-shield-halved mr-1 text-[#0A9800]"></i>@Lang.payment_security_message</p>
+        </div>
+    </div>
+</div>
+
 <!-- Order Summary Screen (from Figma) -->
 <div id="orderSummaryModal" class="hidden fixed inset-0 z-[150] bg-black/60 flex items-center justify-center p-0 font-bricolage backdrop-blur-[2px]">
         <div class="w-full h-full bg-white flex flex-col animate__animated animate__fadeInRight animate__faster shadow-2xl relative lg:max-w-[414px]">
@@ -446,7 +466,7 @@ else
                         </div>
                     </div>
         
-                    <p class="text-[14px] font-bold text-[#555] text-center px-4 leading-snug mt-0 font-bricolage">@Lang.millions_otp_instruction</p>
+                    <p id="paymentCodeInstruction" class="text-[14px] font-bold text-[#555] text-center px-4 leading-snug mt-0 font-bricolage">@Lang.millions_otp_instruction</p>
         
                     <!-- 6-Slots OTP Grid (Large Gray Blocks) -->
                     <div class="flex items-center justify-center gap-2 w-full mt-1 px-2" id="otpInputs">
@@ -960,6 +980,9 @@ else
 
         // --- Order Summary Modal Logic ---
         // --- Payment Preparation & Order Summary Logic ---
+        let pendingTickets = [];
+        let currentChannelPayment = '@Constants.BASIC_WALLET_TICKET';
+
         function preparePayment(event) {
             const isBSMode = @(Model.termType == Constants.PIC10_BIGSMALL_CODE || Model.termType == Constants.PIC10_ODDEVEN_CODE ? "true" : "false");
             const tickets = [];
@@ -1001,19 +1024,24 @@ else
                 }
             }
 
+            pendingTickets = tickets;
+            $("#paymentChannelModal").removeClass("hidden").addClass("flex");
+        }
+
+        function hidePaymentChannelModal() {
+            $("#paymentChannelModal").removeClass("flex").addClass("hidden");
+        }
+
+        function selectPaymentChannel(channelPayment) {
+            currentChannelPayment = channelPayment;
+            hidePaymentChannelModal();
+
             const requestData = {
                 gameId: "@Model.termType",
-                ticket: tickets
+                ticket: pendingTickets,
+                channelPayment: channelPayment
             };
 
-            const btn = event ? event.currentTarget : null;
-            const originalText = btn ? btn.innerHTML : "@Lang.millions_payment";
-            if (btn) {
-                btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Loading...';
-                btn.disabled = true;
-            }
-
-
             $.ajax({
                 url: subDomain + '@Url.Action("ConfirmTicketData", "Home")',
                 type: 'POST',
@@ -1021,23 +1049,14 @@ else
                 data: JSON.stringify(requestData),
                 success: function(data) {
 
-                    if (btn) {
-                        btn.innerHTML = originalText;
-                        btn.disabled = false;
-                    }
-                    if (data.responseCode === "0") {
-                        showOrderSummary(data, tickets);
+                    if (data.responseCode === "0" || data.responseCode === "0000") {
+                        showOrderSummary(data, pendingTickets);
                     } else {
                         showNotification(data.responseMessage || "Confirmation failed", data.responseCode);
                     }
                 },
                 error: function(err) {
 
-                    if (btn) {
-                        btn.innerHTML = originalText;
-                        btn.disabled = false;
-                    }
-
                     showNotification("Network error occurred.", "warning");
                 }
             });
@@ -1110,6 +1129,11 @@ else
         }
 
         function confirmCheckout(btn) {
+            if (currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET') {
+                showOtpModal(true);
+                return;
+            }
+
             const originalText = $(btn).html();
             $(btn).prop('disabled', true).html('<i class="fa-solid fa-spinner fa-spin mr-2"></i>Processing...');
 
@@ -1121,7 +1145,7 @@ else
                     $(btn).prop('disabled', false).html(originalText);
                     
                     if (data.responseCode === "0") {
-                        showOtpModal();
+                        showOtpModal(false);
                     } else {
                         showNotification(data.responseMessage || "Failed to send OTP", data.responseCode);
                     }
@@ -1134,7 +1158,7 @@ else
         }
 
         let otpInterval;
-        function showOtpModal() {
+        function showOtpModal(isWalletPin) {
             hideOrderSummary();
             $("#otpModal").removeClass("hidden").addClass("flex");
             $("#otpInputs input").val("");
@@ -1177,7 +1201,14 @@ else
             $("#otpTicketTypeContainer").append(`<span class='text-[17px] font-[900] ${typeColorClass} italic'>${displayName}</span>`);
             
             $("#otpTotalAmount").text($("#summaryTotalAmount").text());
-            startOtpTimer(60);
+            if (isWalletPin) {
+                $("#paymentCodeInstruction").text('@Html.Raw(Lang.payment_wallet_pin_instruction.Replace("'", "\\'"))');
+                $("#otpTimer, #resendOtpBtn").addClass("hidden");
+                clearInterval(otpInterval);
+            } else {
+                $("#paymentCodeInstruction").text('@Html.Raw(Lang.millions_otp_instruction.Replace("'", "\\'"))');
+                startOtpTimer(60);
+            }
         }
 
         // Add backspace support for OTP inputs
@@ -1284,7 +1315,7 @@ else
             });
             
             if (otpCode.length < 6) {
-                showNotification("Please enter full 6-digit OTP", "warning");
+                showNotification(currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET' ? "Please enter full 6-digit PIN" : "Please enter full 6-digit OTP", "warning");
                 return;
             }
 
@@ -1293,7 +1324,9 @@ else
 
             const finalData = {
                 transIdByTicket: currentTransId,
-                paymentCode: otpCode
+                paymentCode: currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET' ? null : otpCode,
+                walletPin: currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET' ? otpCode : null,
+                channelPayment: currentChannelPayment
             };
 
             // Call the unified backend action that verifies OTP and then buys the ticket

+ 1 - 0
website/Common/Constants.cs

@@ -106,6 +106,7 @@ namespace LotteryWebApp.Common
 
         public const string BASIC_WALLET_TICKET = "0";
         public const string NATCASH_WALLET_TICKET = "1";
+        public const string WALLET_PAYMENT_CHANNEL = "WALLET";
 
         public const string BASIC_WALLET_TRANSFER = "1";
         public const string NATCASH_WALLET_TRANSFER = "2";

+ 8 - 0
website/Languages/Lang.Designer.cs

@@ -5806,5 +5806,13 @@ namespace LotteryWebApp.Languages {
                 return ResourceManager.GetString("your_password_reset", resourceCulture);
             }
         }
+        public static string payment_method_title { get { return ResourceManager.GetString("payment_method_title", resourceCulture); } }
+        public static string payment_method_subtitle { get { return ResourceManager.GetString("payment_method_subtitle", resourceCulture); } }
+        public static string payment_main_account { get { return ResourceManager.GetString("payment_main_account", resourceCulture); } }
+        public static string payment_main_account_desc { get { return ResourceManager.GetString("payment_main_account_desc", resourceCulture); } }
+        public static string payment_wallet { get { return ResourceManager.GetString("payment_wallet", resourceCulture); } }
+        public static string payment_wallet_desc { get { return ResourceManager.GetString("payment_wallet_desc", resourceCulture); } }
+        public static string payment_security_message { get { return ResourceManager.GetString("payment_security_message", resourceCulture); } }
+        public static string payment_wallet_pin_instruction { get { return ResourceManager.GetString("payment_wallet_pin_instruction", resourceCulture); } }
     }
 }

+ 9 - 1
website/Languages/Lang.fr.resx

@@ -2035,4 +2035,12 @@ We’ll help you create an account in a few easy steps.</value>
   <data name="millions_jackpot_rule7_desc3" xml:space="preserve">
     <value>In case of any dispute, Natcom's decision shall be final.</value>
   </data>
-</root>
+  <data name="payment_method_title" xml:space="preserve"><value>Choose payment method</value></data>
+  <data name="payment_method_subtitle" xml:space="preserve"><value>Select an account to complete your purchase</value></data>
+  <data name="payment_main_account" xml:space="preserve"><value>Main account</value></data>
+  <data name="payment_main_account_desc" xml:space="preserve"><value>Pay with your mobile balance</value></data>
+  <data name="payment_wallet" xml:space="preserve"><value>WALLET</value></data>
+  <data name="payment_wallet_desc" xml:space="preserve"><value>Secure payment with wallet PIN</value></data>
+  <data name="payment_security_message" xml:space="preserve"><value>Your payment information is protected</value></data>
+  <data name="payment_wallet_pin_instruction" xml:space="preserve"><value>Enter your WALLET PIN</value></data>
+</root>

+ 9 - 1
website/Languages/Lang.resx

@@ -2036,4 +2036,12 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
   <data name="millions_jackpot_rule7_desc3" xml:space="preserve">
     <value>Si gen nenpòt diskisyon, desizyon Natcom se desizyon final la.</value>
   </data>
-</root>
+  <data name="payment_method_title" xml:space="preserve"><value>Chwazi metòd peman</value></data>
+  <data name="payment_method_subtitle" xml:space="preserve"><value>Chwazi yon kont pou fini acha ou</value></data>
+  <data name="payment_main_account" xml:space="preserve"><value>Kont prensipal</value></data>
+  <data name="payment_main_account_desc" xml:space="preserve"><value>Peye avèk balans mobil ou</value></data>
+  <data name="payment_wallet" xml:space="preserve"><value>WALLET</value></data>
+  <data name="payment_wallet_desc" xml:space="preserve"><value>Peman sekirize avèk PIN wallet la</value></data>
+  <data name="payment_security_message" xml:space="preserve"><value>Enfòmasyon peman ou pwoteje</value></data>
+  <data name="payment_wallet_pin_instruction" xml:space="preserve"><value>Antre PIN WALLET ou</value></data>
+</root>

+ 24 - 0
website/Service/ApiFunctions.cs

@@ -212,6 +212,30 @@ namespace LotteryWebApp.Service
             return deptObj;
         }
 
+        public ConfirmTicketDataResponse BuyTicketV2Api(IConfiguration configuration, ConfirmTicketDataRequest confirmTicketDataRequest)
+        {
+            confirmTicketDataRequest.serviceId = ApiConstants.SERVICE_ID;
+            confirmTicketDataRequest.channel = configuration.GetSection("channel").Value;
+            confirmTicketDataRequest.channelPayment = null;
+
+            String url = configuration.GetSection("buy_ticketv2").Value;
+            String jsonData = SendPost(confirmTicketDataRequest, ApiConstants.SERVICE_ID, url);
+            ConfirmTicketDataResponse deptObj = JsonSerializer.Deserialize<ConfirmTicketDataResponse>(jsonData);
+            return deptObj;
+        }
+        public ConfirmBuyingTicketResponse ConfirmBuyingTicketV2Api(IConfiguration configuration, ConfirmBuyingTicketRequest confirmBuyingTicketRequest, string channelPayment = null, string walletPin = null)
+        {
+            confirmBuyingTicketRequest.serviceId = ApiConstants.SERVICE_ID;
+            confirmBuyingTicketRequest.channel = configuration.GetSection("channel").Value;
+            confirmBuyingTicketRequest.channelPayment = channelPayment;
+            confirmBuyingTicketRequest.walletPin = walletPin;
+
+            String url = configuration.GetSection("confirm_buying_ticketv2").Value;
+            String jsonData = SendPost(confirmBuyingTicketRequest, ApiConstants.SERVICE_ID, url);
+            ConfirmBuyingTicketResponse deptObj = JsonSerializer.Deserialize<ConfirmBuyingTicketResponse>(jsonData);
+            return deptObj;
+        }
+
         public UserTicketResponse GetUserTicketApi(IConfiguration configuration, UserTicketRequest userTicketRequest)
         {
             userTicketRequest.serviceId = ApiConstants.SERVICE_ID;

+ 4 - 0
website/Service/ApiModels.cs

@@ -263,6 +263,8 @@ namespace LotteryWebApp.Service
         public string token { get; set; }
         public string gameId { get; set; }
         public string promotionCode { get; set; }
+        [Newtonsoft.Json.JsonProperty(NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+        public string channelPayment { get; set; }
         public List<TicketInfo> ticket { get; set; }
     }
 
@@ -288,6 +290,8 @@ namespace LotteryWebApp.Service
         public string serviceId { get; set; }
         public string token { get; set; }
         public string paymentCode { get; set; }
+        public string walletPin { get; set; }
+        public string channelPayment { get; set; }
         public string transIdByTicket { get; set; }
     }
 

+ 1 - 1
website/Views/Shared/_Layout.cshtml

@@ -206,7 +206,7 @@
                 </div>
                 <div>
                     <a class="title-navigator">@Lang.buy_ticket</a>
-                </div>
+               </div>
             </div>
             <div class="navigator-footer-button @(navigator == Constants.HISTORY_NAVIGATOR ? "active" : "")"
                  onclick="navigatorClick('History'); return 0;">

+ 3 - 0
website/appsettings.json

@@ -35,6 +35,9 @@
   "auto_login": "http://10.228.102.57/privateapi/intergame/getUserInfor",
   "check_account": "http://171.244.50.177:8989/mytell/api/userApi/usersRegister/data",
 
+  "buy_ticketv2": "http://171.244.50.177:8900/api/loto/buyTicketV2/data",
+  "confirm_buying_ticketv2": "http://171.244.50.177:8900/api/loto/confirmTicketV2/data",
+
   "Logging": {
     "LogLevel": {
       "Default": "Information",