|
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.AspNetCore.Mvc.RazorPages;
- using Microsoft.Extensions.Options;
- using Microsoft.Extensions.Caching.Memory;
- using Altob.NtuInvoiceGateway.Models;
- using System.Text;
- using System.Text.Json;
- using System.Linq;
-
- namespace Altob.NtuInvoiceGateway.Pages;
-
- [IgnoreAntiforgeryToken] // 允許外部系統 POST JSON 請求
- public class InvoiceModel : PageModel
- {
- private readonly ILogger<InvoiceModel> _logger;
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly IMemoryCache _memoryCache;
- private readonly CompanyInfo _companyInfo;
- private readonly InvoiceApiOptions _invoiceApiOptions;
- private readonly DonateCodeApiOptions _donateCodeApiOptions;
- private const string ServiceName = "NtuInvoiceGateway";
- private const string DonateCodeCacheKey = "DonateCodeList";
-
- public InvoiceModel(
- ILogger<InvoiceModel> logger,
- IHttpClientFactory httpClientFactory,
- IMemoryCache memoryCache,
- IOptions<CompanyInfo> companyInfo,
- IOptions<InvoiceApiOptions> invoiceApiOptions,
- IOptions<DonateCodeApiOptions> donateCodeApiOptions)
- {
- _logger = logger;
- _httpClientFactory = httpClientFactory;
- _memoryCache = memoryCache;
- _companyInfo = companyInfo.Value;
- _invoiceApiOptions = invoiceApiOptions.Value;
- _donateCodeApiOptions = donateCodeApiOptions.Value;
- }
-
- [BindProperty(SupportsGet = true)]
- public InvoiceRequest InvoiceData { get; set; } = new();
-
- public string? ErrorMessage { get; set; }
- [TempData]
- public string? SuccessMessage { get; set; }
- public string CompanyName => _companyInfo.Name;
- public string CompanyTaxId => _companyInfo.TaxId;
-
- // 顯示用的發票資訊(唯讀)
- public string DisplayTransDateTime { get; set; } = string.Empty;
- public string DisplayTransAmount { get; set; } = string.Empty;
-
- public void OnGet()
- {
- RestoreDisplayValuesFromTempData();
- NormalizeInvoiceData();
- // 如果有 GET 參數,設置顯示資訊
- DisplayTransDateTime = InvoiceData.TransDateTime;
- DisplayTransAmount = InvoiceData.TransAmount;
- }
-
- public async Task<IActionResult> OnGetDonateCodesAsync()
- {
- var actionName = nameof(OnGetDonateCodesAsync);
-
- try
- {
- // 檢查 Cache 是否有資料
- if (_memoryCache.TryGetValue(DonateCodeCacheKey, out List<DonateGroup>? cachedData) && cachedData != null)
- {
- _logger.LogInformation("{ServiceName} - {ActionName} returning cached donate codes, count: {Count}",
- ServiceName, actionName, cachedData.Count);
- return new JsonResult(cachedData);
- }
-
- // Cache 中沒有資料,從 API 取得
- if (string.IsNullOrWhiteSpace(_donateCodeApiOptions.Endpoint))
- {
- _logger.LogError("{ServiceName} - {ActionName} DonateCode API endpoint is not configured", ServiceName, actionName);
- return new JsonResult(new List<DonateGroup>()) { StatusCode = 500 };
- }
-
- _logger.LogInformation("{ServiceName} - {ActionName} fetching donate codes from API: {Endpoint}",
- ServiceName, actionName, _donateCodeApiOptions.Endpoint);
-
- var httpClient = _httpClientFactory.CreateClient();
- var response = await httpClient.GetAsync(_donateCodeApiOptions.Endpoint);
-
- if (!response.IsSuccessStatusCode)
- {
- _logger.LogError("{ServiceName} - {ActionName} failed to fetch donate codes, status: {StatusCode}",
- ServiceName, actionName, response.StatusCode);
- return new JsonResult(new List<DonateGroup>()) { StatusCode = (int)response.StatusCode };
- }
-
- var responseContent = await response.Content.ReadAsStringAsync();
- var donateCodes = JsonSerializer.Deserialize<List<DonateGroup>>(responseContent, new JsonSerializerOptions
- {
- PropertyNameCaseInsensitive = true
- });
-
- if (donateCodes != null && donateCodes.Count > 0)
- {
- // 按照 Seq 欄位排序
- var sortedDonateCodes = donateCodes.OrderBy(d => d.Seq).ToList();
-
- // 將資料存入 Cache,有效期限 7 天
- var cacheOptions = new MemoryCacheEntryOptions
- {
- AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(7)
- };
- _memoryCache.Set(DonateCodeCacheKey, sortedDonateCodes, cacheOptions);
-
- _logger.LogInformation("{ServiceName} - {ActionName} cached {Count} donate codes for 7 days",
- ServiceName, actionName, sortedDonateCodes.Count);
-
- return new JsonResult(sortedDonateCodes);
- }
-
- _logger.LogWarning("{ServiceName} - {ActionName} received empty donate code list from API", ServiceName, actionName);
- return new JsonResult(new List<DonateGroup>());
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "{ServiceName} - {ActionName} error fetching donate codes", ServiceName, actionName);
- return new JsonResult(new List<DonateGroup>()) { StatusCode = 500 };
- }
- }
-
- public async Task<IActionResult> OnPostAsync()
- {
- NormalizeInvoiceData();
- var actionName = nameof(OnPostAsync);
- _logger.LogInformation("{ServiceName} - {ActionName} received JSON redirect payload {@InvoiceData}",
- ServiceName, actionName, InvoiceData);
- // 檢查是否為 JSON 請求(外部系統轉頁)
- if (Request.ContentType?.Contains("application/json") == true)
- {
- try
- {
- // 從 Request Body 讀取 JSON 資料
- using var reader = new StreamReader(Request.Body);
- var jsonContent = await reader.ReadToEndAsync();
- _logger.LogInformation("{ServiceName} - {ActionName} JSON payload content: {JsonPayload}", ServiceName, actionName, jsonContent);
-
- var jsonData = JsonSerializer.Deserialize<InvoiceRequest>(jsonContent, new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- PropertyNameCaseInsensitive = true
- });
-
- if (jsonData != null)
- {
- InvoiceData = jsonData;
- NormalizeInvoiceData();
- DisplayTransDateTime = InvoiceData.TransDateTime;
- DisplayTransAmount = InvoiceData.TransAmount;
- ModelState.Clear(); // ensure Razor uses the JSON payload values
-
- _logger.LogInformation("{ServiceName} - {ActionName} received redirect for OrderID: {OrderID}", ServiceName, actionName, InvoiceData.OrderID);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "{ServiceName} - {ActionName} error parsing JSON request", ServiceName, actionName);
- ErrorMessage = "接收轉頁資料時發生錯誤";
- }
- }
- // 顯示表單讓用戶填寫
- return Page();
- }
-
- public async Task<IActionResult> OnPostToEndPointAsync()
- {
- NormalizeInvoiceData();
- // 處理表單提交
- // 保留發票資訊顯示
- DisplayTransDateTime = InvoiceData.TransDateTime;
- DisplayTransAmount = InvoiceData.TransAmount;
- var actionName = nameof(OnPostToEndPointAsync);
- _logger.LogInformation("{ServiceName} - {ActionName} received invoice submission payload {@InvoiceData}",
- ServiceName, actionName, InvoiceData);
-
- // 判斷是否為初始轉頁(三個選項都沒填寫)
- bool isInitialRedirect = string.IsNullOrEmpty(InvoiceData.Email) &&
- string.IsNullOrEmpty(InvoiceData.CarrierID) &&
- string.IsNullOrEmpty(InvoiceData.BuyerIdentifier) &&
- string.IsNullOrEmpty(InvoiceData.LoveCode);
-
- // 如果是初始轉頁,直接顯示表單讓用戶填寫
- if (isInitialRedirect)
- {
- return Page();
- }
-
- if (!ModelState.IsValid)
- {
- var invalidFields = ModelState
- .Where(entry => entry.Value?.Errors.Count > 0)
- .Select(entry =>
- {
- var fieldName = string.IsNullOrEmpty(entry.Key) ? "表單" : entry.Key;
- var messages = string.Join("、", entry.Value!.Errors.Select(e => e.ErrorMessage));
- return $"{fieldName}:{messages}";
- });
-
- ErrorMessage = "資料驗證失敗,請檢查欄位輸入";
- _logger.LogWarning("{ServiceName} - {ActionName} validation failed: {ValidationErrors}", ServiceName, actionName, string.Join(" | ", invalidFields));
- return Page();
- }
-
- // 驗證 Email, CarrierID, BuyerIdentifier 至少填寫一個
- int filledCount = 0;
- if (!string.IsNullOrEmpty(InvoiceData.Email)) filledCount++;
- if (!string.IsNullOrEmpty(InvoiceData.CarrierID)) filledCount++;
- if (!string.IsNullOrEmpty(InvoiceData.BuyerIdentifier)) filledCount++;
- if (!string.IsNullOrEmpty(InvoiceData.LoveCode)) filledCount++;
-
- if (filledCount == 0)
- {
- ErrorMessage = "Email、手機條碼、購買者統編至少需填寫一個";
- return Page();
- }
-
- if (string.IsNullOrWhiteSpace(_invoiceApiOptions.Endpoint))
- {
- ErrorMessage = "未設定外部發票 API 的位址";
- _logger.LogError("{ServiceName} - {ActionName} Invoice API endpoint is not configured", ServiceName, actionName);
- return Page();
- }
-
- try
- {
- // 呼叫 TODO API(這裡使用規格中的範例 API)
- // http://192.168.110.72:22055/api/Intella/invoiceInfo
- // {
- // "identifier": "12345678",
- // "transDateTime": "2026/01/08 12:00:00",
- // "transAmount": "30",
- // "deviceID": "test",
- // "locationID": "1",
- // "carPlateNum": "ABC1235",
- // "orderID": "260109_1",
- // "email": "",
- // "carrierID": "/ab12345",
- // "buyerIdentifier": "",
- // "taxType": "1",
- // "loveCode": ""
- // }
- var httpClient = _httpClientFactory.CreateClient();
- var requestBody = JsonSerializer.Serialize(InvoiceData, new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
- });
- _logger.LogInformation("{ServiceName} - {ActionName} sending API payload: {RequestBody}",
- ServiceName, actionName, requestBody);
-
- var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
-
- var response = await httpClient.PostAsync(_invoiceApiOptions.Endpoint, content);
-
- var responseContent = await response.Content.ReadAsStringAsync();
- _logger.LogInformation("{ServiceName} - {ActionName} received API response status {StatusCode} body: {ResponseBody}",
- ServiceName, actionName, response.StatusCode, responseContent);
- var apiResponse = JsonSerializer.Deserialize<InvoiceResponse>(responseContent, new JsonSerializerOptions
- {
- PropertyNameCaseInsensitive = true
- });
-
- if (!response.IsSuccessStatusCode)
- {
- ErrorMessage = $"送出失敗:{apiResponse?.msg ?? response.StatusCode.ToString()}";
- _logger.LogWarning("{ServiceName} - {ActionName} submission failed with HTTP {StatusCode} for OrderID: {OrderID}",
- ServiceName, actionName, response.StatusCode, InvoiceData.OrderID);
- return Page();
- }
-
- if (apiResponse?.msgCode == "0000")
- {
- SuccessMessage = "發票資訊送出成功!";
- TempData[nameof(DisplayTransDateTime)] = DisplayTransDateTime;
- TempData[nameof(DisplayTransAmount)] = DisplayTransAmount;
- _logger.LogInformation("{ServiceName} - {ActionName} submitted successfully for OrderID: {OrderID}",
- ServiceName, actionName, InvoiceData.OrderID);
- return RedirectToPage();
- }
- else
- {
- ErrorMessage = $"送出失敗:{apiResponse?.msg ?? "未知錯誤"}";
- _logger.LogWarning("{ServiceName} - {ActionName} submission failed: {Message}", ServiceName, actionName, apiResponse?.msg);
- }
- }
- catch (Exception ex)
- {
- #if DEBUG
- ErrorMessage = $"系統錯誤:{ex.Message}";
- #else
- ErrorMessage = $"系統錯誤";
- #endif
- _logger.LogError(ex, "{ServiceName} - {ActionName} error submitting invoice for OrderID: {OrderID}",
- ServiceName, actionName, InvoiceData.OrderID);
- }
-
- return Page();
- }
-
- private void NormalizeInvoiceData()
- {
- InvoiceData ??= new InvoiceRequest();
-
- InvoiceData.Identifier = InvoiceData.Identifier ?? string.Empty;
- InvoiceData.TransDateTime = InvoiceData.TransDateTime ?? string.Empty;
- InvoiceData.TransAmount = InvoiceData.TransAmount ?? string.Empty;
- InvoiceData.DeviceID = InvoiceData.DeviceID ?? string.Empty;
- InvoiceData.LocationID = InvoiceData.LocationID ?? string.Empty;
- InvoiceData.CarPlateNum = InvoiceData.CarPlateNum ?? string.Empty;
- InvoiceData.OrderID = InvoiceData.OrderID ?? string.Empty;
- InvoiceData.TaxType = InvoiceData.TaxType ?? string.Empty;
-
- InvoiceData.Email = InvoiceData.Email ?? string.Empty;
- InvoiceData.CarrierID = InvoiceData.CarrierID ?? string.Empty;
- InvoiceData.BuyerIdentifier = InvoiceData.BuyerIdentifier ?? string.Empty;
- InvoiceData.LoveCode = InvoiceData.LoveCode ?? string.Empty;
- }
-
- private void RestoreDisplayValuesFromTempData()
- {
- if (TempData.TryGetValue(nameof(DisplayTransDateTime), out var transDateObj) &&
- transDateObj is string transDate)
- {
- DisplayTransDateTime = transDate;
- }
-
- if (TempData.TryGetValue(nameof(DisplayTransAmount), out var amountObj) &&
- amountObj is string amount)
- {
- DisplayTransAmount = amount;
- }
- }
- }
|