提供給台大案英特拉線上繳費填寫發票資訊用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

260 line
11KB

  1. using Microsoft.AspNetCore.Mvc;
  2. using Microsoft.AspNetCore.Mvc.RazorPages;
  3. using Microsoft.Extensions.Options;
  4. using Altob.NtuInvoiceGateway.Models;
  5. using System.Text;
  6. using System.Text.Json;
  7. using System.Linq;
  8. namespace Altob.NtuInvoiceGateway.Pages;
  9. [IgnoreAntiforgeryToken] // 允許外部系統 POST JSON 請求
  10. public class InvoiceModel : PageModel
  11. {
  12. private readonly ILogger<InvoiceModel> _logger;
  13. private readonly IHttpClientFactory _httpClientFactory;
  14. private readonly CompanyInfo _companyInfo;
  15. private readonly InvoiceApiOptions _invoiceApiOptions;
  16. private const string ServiceName = "NtuInvoiceGateway";
  17. public InvoiceModel(
  18. ILogger<InvoiceModel> logger,
  19. IHttpClientFactory httpClientFactory,
  20. IOptions<CompanyInfo> companyInfo,
  21. IOptions<InvoiceApiOptions> invoiceApiOptions)
  22. {
  23. _logger = logger;
  24. _httpClientFactory = httpClientFactory;
  25. _companyInfo = companyInfo.Value;
  26. _invoiceApiOptions = invoiceApiOptions.Value;
  27. }
  28. [BindProperty]
  29. public InvoiceRequest InvoiceData { get; set; } = new();
  30. public string? ErrorMessage { get; set; }
  31. [TempData]
  32. public string? SuccessMessage { get; set; }
  33. public string CompanyName => _companyInfo.Name;
  34. public string CompanyTaxId => _companyInfo.TaxId;
  35. // 顯示用的發票資訊(唯讀)
  36. public string DisplayTransDateTime { get; set; } = string.Empty;
  37. public string DisplayTransAmount { get; set; } = string.Empty;
  38. public void OnGet()
  39. {
  40. RestoreDisplayValuesFromTempData();
  41. // GET 方法保留為空,主要接收方式改為 POST JSON
  42. }
  43. public async Task<IActionResult> OnPostAsync()
  44. {
  45. NormalizeInvoiceData();
  46. var actionName = nameof(OnPostAsync);
  47. _logger.LogInformation("{ServiceName} - {ActionName} received JSON redirect payload {@InvoiceData}",
  48. ServiceName, actionName, InvoiceData);
  49. // 檢查是否為 JSON 請求(外部系統轉頁)
  50. if (Request.ContentType?.Contains("application/json") == true)
  51. {
  52. try
  53. {
  54. // 從 Request Body 讀取 JSON 資料
  55. using var reader = new StreamReader(Request.Body);
  56. var jsonContent = await reader.ReadToEndAsync();
  57. _logger.LogInformation("{ServiceName} - {ActionName} JSON payload content: {JsonPayload}", ServiceName, actionName, jsonContent);
  58. var jsonData = JsonSerializer.Deserialize<InvoiceRequest>(jsonContent, new JsonSerializerOptions
  59. {
  60. PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  61. PropertyNameCaseInsensitive = true
  62. });
  63. if (jsonData != null)
  64. {
  65. InvoiceData = jsonData;
  66. NormalizeInvoiceData();
  67. DisplayTransDateTime = InvoiceData.TransDateTime;
  68. DisplayTransAmount = InvoiceData.TransAmount;
  69. ModelState.Clear(); // ensure Razor uses the JSON payload values
  70. _logger.LogInformation("{ServiceName} - {ActionName} received redirect for OrderID: {OrderID}", ServiceName, actionName, InvoiceData.OrderID);
  71. }
  72. }
  73. catch (Exception ex)
  74. {
  75. _logger.LogError(ex, "{ServiceName} - {ActionName} error parsing JSON request", ServiceName, actionName);
  76. ErrorMessage = "接收轉頁資料時發生錯誤";
  77. }
  78. }
  79. // 顯示表單讓用戶填寫
  80. return Page();
  81. }
  82. public async Task<IActionResult> OnPostToEndPointAsync()
  83. {
  84. NormalizeInvoiceData();
  85. // 處理表單提交
  86. // 保留發票資訊顯示
  87. DisplayTransDateTime = InvoiceData.TransDateTime;
  88. DisplayTransAmount = InvoiceData.TransAmount;
  89. var actionName = nameof(OnPostToEndPointAsync);
  90. _logger.LogInformation("{ServiceName} - {ActionName} received invoice submission payload {@InvoiceData}",
  91. ServiceName, actionName, InvoiceData);
  92. // 判斷是否為初始轉頁(三個選項都沒填寫)
  93. bool isInitialRedirect = string.IsNullOrEmpty(InvoiceData.Email) &&
  94. string.IsNullOrEmpty(InvoiceData.CarrierID) &&
  95. string.IsNullOrEmpty(InvoiceData.BuyerIdentifier);
  96. // 如果是初始轉頁,直接顯示表單讓用戶填寫
  97. if (isInitialRedirect)
  98. {
  99. return Page();
  100. }
  101. if (!ModelState.IsValid)
  102. {
  103. var invalidFields = ModelState
  104. .Where(entry => entry.Value?.Errors.Count > 0)
  105. .Select(entry =>
  106. {
  107. var fieldName = string.IsNullOrEmpty(entry.Key) ? "表單" : entry.Key;
  108. var messages = string.Join("、", entry.Value!.Errors.Select(e => e.ErrorMessage));
  109. return $"{fieldName}:{messages}";
  110. });
  111. ErrorMessage = "資料驗證失敗,請檢查欄位輸入";
  112. _logger.LogWarning("{ServiceName} - {ActionName} validation failed: {ValidationErrors}", ServiceName, actionName, string.Join(" | ", invalidFields));
  113. return Page();
  114. }
  115. // 驗證 Email, CarrierID, BuyerIdentifier 至少填寫一個
  116. int filledCount = 0;
  117. if (!string.IsNullOrEmpty(InvoiceData.Email)) filledCount++;
  118. if (!string.IsNullOrEmpty(InvoiceData.CarrierID)) filledCount++;
  119. if (!string.IsNullOrEmpty(InvoiceData.BuyerIdentifier)) filledCount++;
  120. // if (!string.IsNullOrEmpty(InvoiceData.LoveCode)) filledCount++;
  121. if (filledCount == 0)
  122. {
  123. ErrorMessage = "Email、手機條碼、購買者統編至少需填寫一個";
  124. return Page();
  125. }
  126. if (string.IsNullOrWhiteSpace(_invoiceApiOptions.Endpoint))
  127. {
  128. ErrorMessage = "未設定外部發票 API 的位址";
  129. _logger.LogError("{ServiceName} - {ActionName} Invoice API endpoint is not configured", ServiceName, actionName);
  130. return Page();
  131. }
  132. try
  133. {
  134. // 呼叫 TODO API(這裡使用規格中的範例 API)
  135. // http://192.168.110.72:22055/api/Intella/invoiceInfo
  136. // {
  137. // "identifier": "12345678",
  138. // "transDateTime": "2026/01/08 12:00:00",
  139. // "transAmount": "30",
  140. // "deviceID": "test",
  141. // "locationID": "1",
  142. // "carPlateNum": "ABC1235",
  143. // "orderID": "260109_1",
  144. // "email": "",
  145. // "carrierID": "/ab12345",
  146. // "buyerIdentifier": "",
  147. // "taxType": "1",
  148. // "loveCode": ""
  149. // }
  150. var httpClient = _httpClientFactory.CreateClient();
  151. var requestBody = JsonSerializer.Serialize(InvoiceData, new JsonSerializerOptions
  152. {
  153. PropertyNamingPolicy = JsonNamingPolicy.CamelCase
  154. });
  155. _logger.LogInformation("{ServiceName} - {ActionName} sending API payload: {RequestBody}",
  156. ServiceName, actionName, requestBody);
  157. var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
  158. var response = await httpClient.PostAsync(_invoiceApiOptions.Endpoint, content);
  159. var responseContent = await response.Content.ReadAsStringAsync();
  160. _logger.LogInformation("{ServiceName} - {ActionName} received API response status {StatusCode} body: {ResponseBody}",
  161. ServiceName, actionName, response.StatusCode, responseContent);
  162. var apiResponse = JsonSerializer.Deserialize<InvoiceResponse>(responseContent, new JsonSerializerOptions
  163. {
  164. PropertyNameCaseInsensitive = true
  165. });
  166. if (!response.IsSuccessStatusCode)
  167. {
  168. ErrorMessage = $"提交失敗:{apiResponse?.msg ?? response.StatusCode.ToString()}";
  169. _logger.LogWarning("{ServiceName} - {ActionName} submission failed with HTTP {StatusCode} for OrderID: {OrderID}",
  170. ServiceName, actionName, response.StatusCode, InvoiceData.OrderID);
  171. return Page();
  172. }
  173. if (apiResponse?.msgCode == "0000")
  174. {
  175. SuccessMessage = "發票資訊提交成功!";
  176. TempData[nameof(DisplayTransDateTime)] = DisplayTransDateTime;
  177. TempData[nameof(DisplayTransAmount)] = DisplayTransAmount;
  178. _logger.LogInformation("{ServiceName} - {ActionName} submitted successfully for OrderID: {OrderID}",
  179. ServiceName, actionName, InvoiceData.OrderID);
  180. return RedirectToPage();
  181. }
  182. else
  183. {
  184. ErrorMessage = $"提交失敗:{apiResponse?.msg ?? "未知錯誤"}";
  185. _logger.LogWarning("{ServiceName} - {ActionName} submission failed: {Message}", ServiceName, actionName, apiResponse?.msg);
  186. }
  187. }
  188. catch (Exception ex)
  189. {
  190. #if DEBUG
  191. ErrorMessage = $"系統錯誤:{ex.Message}";
  192. #else
  193. ErrorMessage = $"系統錯誤";
  194. #endif
  195. _logger.LogError(ex, "{ServiceName} - {ActionName} error submitting invoice for OrderID: {OrderID}",
  196. ServiceName, actionName, InvoiceData.OrderID);
  197. }
  198. return Page();
  199. }
  200. private void NormalizeInvoiceData()
  201. {
  202. InvoiceData ??= new InvoiceRequest();
  203. InvoiceData.Identifier = InvoiceData.Identifier ?? string.Empty;
  204. InvoiceData.TransDateTime = InvoiceData.TransDateTime ?? string.Empty;
  205. InvoiceData.TransAmount = InvoiceData.TransAmount ?? string.Empty;
  206. InvoiceData.DeviceID = InvoiceData.DeviceID ?? string.Empty;
  207. InvoiceData.Email = InvoiceData.Email ?? string.Empty;
  208. InvoiceData.CarrierID = InvoiceData.CarrierID ?? string.Empty;
  209. InvoiceData.LocationID = InvoiceData.LocationID ?? string.Empty;
  210. InvoiceData.CarPlateNum = InvoiceData.CarPlateNum ?? string.Empty;
  211. InvoiceData.OrderID = InvoiceData.OrderID ?? string.Empty;
  212. InvoiceData.BuyerIdentifier = InvoiceData.BuyerIdentifier ?? string.Empty;
  213. InvoiceData.LoveCode = InvoiceData.LoveCode ?? string.Empty;
  214. InvoiceData.TaxType = InvoiceData.TaxType ?? string.Empty;
  215. }
  216. private void RestoreDisplayValuesFromTempData()
  217. {
  218. if (TempData.TryGetValue(nameof(DisplayTransDateTime), out var transDateObj) &&
  219. transDateObj is string transDate)
  220. {
  221. DisplayTransDateTime = transDate;
  222. }
  223. if (TempData.TryGetValue(nameof(DisplayTransAmount), out var amountObj) &&
  224. amountObj is string amount)
  225. {
  226. DisplayTransAmount = amount;
  227. }
  228. }
  229. }