| @@ -54,7 +54,6 @@ | |||||
| <div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div> | <div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div> | ||||
| <!-- 隱藏欄位:系統傳入的資料 --> | <!-- 隱藏欄位:系統傳入的資料 --> | ||||
| <input type="hidden" asp-for="InvoiceData.Identifier" /> | |||||
| <input type="hidden" asp-for="InvoiceData.TransDateTime" /> | <input type="hidden" asp-for="InvoiceData.TransDateTime" /> | ||||
| <input type="hidden" asp-for="InvoiceData.TransAmount" /> | <input type="hidden" asp-for="InvoiceData.TransAmount" /> | ||||
| <input type="hidden" asp-for="InvoiceData.DeviceID" /> | <input type="hidden" asp-for="InvoiceData.DeviceID" /> | ||||
| @@ -63,6 +62,21 @@ | |||||
| <input type="hidden" asp-for="InvoiceData.OrderID" /> | <input type="hidden" asp-for="InvoiceData.OrderID" /> | ||||
| <input type="hidden" asp-for="InvoiceData.TaxType" /> | <input type="hidden" asp-for="InvoiceData.TaxType" /> | ||||
| @{ | |||||
| if (string.IsNullOrWhiteSpace(Model.InvoiceData.Identifier)) | |||||
| { | |||||
| Model.InvoiceData.Identifier = Model.CompanyTaxId; | |||||
| } | |||||
| } | |||||
| <div class="mb-3"> | |||||
| <label asp-for="InvoiceData.Identifier" class="form-label">開發票者統編</label> | |||||
| <input asp-for="InvoiceData.Identifier" | |||||
| class="form-control" | |||||
| maxlength="8" /> | |||||
| <span asp-validation-for="InvoiceData.Identifier" class="text-danger"></span> | |||||
| </div> | |||||
| <div class="alert alert-info mb-4"> | <div class="alert alert-info mb-4"> | ||||
| <strong>注意:</strong>以下三個選項只能填寫其中一個 | <strong>注意:</strong>以下三個選項只能填寫其中一個 | ||||
| </div> | </div> | ||||
| @@ -7,20 +7,24 @@ using System.Text.Json; | |||||
| namespace Altob.NtuInvoiceGateway.Pages; | namespace Altob.NtuInvoiceGateway.Pages; | ||||
| [IgnoreAntiforgeryToken] // 允許外部系統 POST JSON 請求 | |||||
| public class InvoiceModel : PageModel | public class InvoiceModel : PageModel | ||||
| { | { | ||||
| private readonly ILogger<InvoiceModel> _logger; | private readonly ILogger<InvoiceModel> _logger; | ||||
| private readonly IHttpClientFactory _httpClientFactory; | private readonly IHttpClientFactory _httpClientFactory; | ||||
| private readonly CompanyInfo _companyInfo; | private readonly CompanyInfo _companyInfo; | ||||
| private readonly InvoiceApiOptions _invoiceApiOptions; | |||||
| public InvoiceModel( | public InvoiceModel( | ||||
| ILogger<InvoiceModel> logger, | ILogger<InvoiceModel> logger, | ||||
| IHttpClientFactory httpClientFactory, | IHttpClientFactory httpClientFactory, | ||||
| IOptions<CompanyInfo> companyInfo) | |||||
| IOptions<CompanyInfo> companyInfo, | |||||
| IOptions<InvoiceApiOptions> invoiceApiOptions) | |||||
| { | { | ||||
| _logger = logger; | _logger = logger; | ||||
| _httpClientFactory = httpClientFactory; | _httpClientFactory = httpClientFactory; | ||||
| _companyInfo = companyInfo.Value; | _companyInfo = companyInfo.Value; | ||||
| _invoiceApiOptions = invoiceApiOptions.Value; | |||||
| } | } | ||||
| [BindProperty] | [BindProperty] | ||||
| @@ -35,55 +39,49 @@ public class InvoiceModel : PageModel | |||||
| public string DisplayTransDateTime { get; set; } = string.Empty; | public string DisplayTransDateTime { get; set; } = string.Empty; | ||||
| public string DisplayTransAmount { get; set; } = string.Empty; | public string DisplayTransAmount { get; set; } = string.Empty; | ||||
| public void OnGet( | |||||
| string? identifier = null, | |||||
| string? transDateTime = null, | |||||
| string? transAmount = null, | |||||
| string? deviceID = null, | |||||
| string? email = null, | |||||
| string? carrierID = null, | |||||
| string? locationID = null, | |||||
| string? carPlateNum = null, | |||||
| string? orderID = null, | |||||
| string? buyerIdentifier = null, | |||||
| // string? loveCode = null, | |||||
| string? taxType = null) | |||||
| public void OnGet() | |||||
| { | { | ||||
| // 從查詢字串接收系統轉頁傳來的資料 | |||||
| if (!string.IsNullOrEmpty(identifier)) | |||||
| InvoiceData.Identifier = identifier; | |||||
| if (!string.IsNullOrEmpty(transDateTime)) | |||||
| { | |||||
| InvoiceData.TransDateTime = transDateTime; | |||||
| DisplayTransDateTime = transDateTime; | |||||
| } | |||||
| if (!string.IsNullOrEmpty(transAmount)) | |||||
| { | |||||
| InvoiceData.TransAmount = transAmount; | |||||
| DisplayTransAmount = transAmount; | |||||
| } | |||||
| if (!string.IsNullOrEmpty(deviceID)) | |||||
| InvoiceData.DeviceID = deviceID; | |||||
| if (!string.IsNullOrEmpty(email)) | |||||
| InvoiceData.Email = email; | |||||
| if (!string.IsNullOrEmpty(carrierID)) | |||||
| InvoiceData.CarrierID = carrierID; | |||||
| if (!string.IsNullOrEmpty(locationID)) | |||||
| InvoiceData.LocationID = locationID; | |||||
| if (!string.IsNullOrEmpty(carPlateNum)) | |||||
| InvoiceData.CarPlateNum = carPlateNum; | |||||
| if (!string.IsNullOrEmpty(orderID)) | |||||
| InvoiceData.OrderID = orderID; | |||||
| if (!string.IsNullOrEmpty(buyerIdentifier)) | |||||
| InvoiceData.BuyerIdentifier = buyerIdentifier; | |||||
| // if (!string.IsNullOrEmpty(loveCode)) | |||||
| // InvoiceData.LoveCode = loveCode; | |||||
| if (!string.IsNullOrEmpty(taxType)) | |||||
| InvoiceData.TaxType = taxType; | |||||
| // GET 方法保留為空,主要接收方式改為 POST JSON | |||||
| } | } | ||||
| public async Task<IActionResult> OnPostAsync() | public async Task<IActionResult> OnPostAsync() | ||||
| { | { | ||||
| // 檢查是否為 JSON 請求(外部系統轉頁) | |||||
| if (Request.ContentType?.Contains("application/json") == true) | |||||
| { | |||||
| try | |||||
| { | |||||
| // 從 Request Body 讀取 JSON 資料 | |||||
| using var reader = new StreamReader(Request.Body); | |||||
| var jsonContent = await reader.ReadToEndAsync(); | |||||
| var jsonData = JsonSerializer.Deserialize<InvoiceRequest>(jsonContent, new JsonSerializerOptions | |||||
| { | |||||
| PropertyNamingPolicy = JsonNamingPolicy.CamelCase, | |||||
| PropertyNameCaseInsensitive = true | |||||
| }); | |||||
| if (jsonData != null) | |||||
| { | |||||
| InvoiceData = jsonData; | |||||
| DisplayTransDateTime = InvoiceData.TransDateTime; | |||||
| DisplayTransAmount = InvoiceData.TransAmount; | |||||
| ModelState.Clear(); // ensure Razor uses the JSON payload values | |||||
| _logger.LogInformation("Received JSON redirect for OrderID: {OrderID}", InvoiceData.OrderID); | |||||
| } | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| _logger.LogError(ex, "Error parsing JSON request"); | |||||
| ErrorMessage = "接收轉頁資料時發生錯誤"; | |||||
| } | |||||
| // 顯示表單讓用戶填寫 | |||||
| return Page(); | |||||
| } | |||||
| // 處理表單提交 | |||||
| // 保留發票資訊顯示 | // 保留發票資訊顯示 | ||||
| DisplayTransDateTime = InvoiceData.TransDateTime; | DisplayTransDateTime = InvoiceData.TransDateTime; | ||||
| DisplayTransAmount = InvoiceData.TransAmount; | DisplayTransAmount = InvoiceData.TransAmount; | ||||
| @@ -117,26 +115,54 @@ public class InvoiceModel : PageModel | |||||
| return Page(); | return Page(); | ||||
| } | } | ||||
| if (string.IsNullOrWhiteSpace(_invoiceApiOptions.Endpoint)) | |||||
| { | |||||
| ErrorMessage = "未設定外部發票 API 的位址"; | |||||
| _logger.LogError("Invoice API endpoint is not configured"); | |||||
| return Page(); | |||||
| } | |||||
| try | try | ||||
| { | { | ||||
| // 呼叫 TODO API(這裡使用規格中的範例 API) | // 呼叫 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 httpClient = _httpClientFactory.CreateClient(); | ||||
| var jsonContent = JsonSerializer.Serialize(InvoiceData, new JsonSerializerOptions | |||||
| var requestBody = JsonSerializer.Serialize(InvoiceData, new JsonSerializerOptions | |||||
| { | { | ||||
| PropertyNamingPolicy = JsonNamingPolicy.CamelCase | PropertyNamingPolicy = JsonNamingPolicy.CamelCase | ||||
| }); | }); | ||||
| var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); | |||||
| var content = new StringContent(requestBody, Encoding.UTF8, "application/json"); | |||||
| // TODO: 替換為實際的 API 端點 | |||||
| var response = await httpClient.PostAsync("YOUR_API_ENDPOINT_HERE", content); | |||||
| var response = await httpClient.PostAsync(_invoiceApiOptions.Endpoint, content); | |||||
| var responseContent = await response.Content.ReadAsStringAsync(); | var responseContent = await response.Content.ReadAsStringAsync(); | ||||
| var apiResponse = JsonSerializer.Deserialize<InvoiceResponse>(responseContent, new JsonSerializerOptions | var apiResponse = JsonSerializer.Deserialize<InvoiceResponse>(responseContent, new JsonSerializerOptions | ||||
| { | { | ||||
| PropertyNamingPolicy = JsonNamingPolicy.CamelCase | |||||
| PropertyNameCaseInsensitive = true | |||||
| }); | }); | ||||
| if (!response.IsSuccessStatusCode) | |||||
| { | |||||
| ErrorMessage = $"提交失敗:{apiResponse?.msg ?? response.StatusCode.ToString()}"; | |||||
| _logger.LogWarning("Invoice submission failed with HTTP {StatusCode} for OrderID: {OrderID}", response.StatusCode, InvoiceData.OrderID); | |||||
| return Page(); | |||||
| } | |||||
| if (apiResponse?.msgCode == "0000") | if (apiResponse?.msgCode == "0000") | ||||
| { | { | ||||
| SuccessMessage = "發票資訊提交成功!"; | SuccessMessage = "發票資訊提交成功!"; | ||||
| @@ -1,4 +1,5 @@ | |||||
| @page | @page | ||||
| @model TestPostInvoiceModel | |||||
| @{ | @{ | ||||
| ViewData["Title"] = "POST 轉頁測試"; | ViewData["Title"] = "POST 轉頁測試"; | ||||
| Layout = null; | Layout = null; | ||||
| @@ -25,12 +26,20 @@ | |||||
| <div class="row mb-3"> | <div class="row mb-3"> | ||||
| <div class="col-md-6"> | <div class="col-md-6"> | ||||
| <label class="form-label">開發票者統編 (Identifier) *</label> | <label class="form-label">開發票者統編 (Identifier) *</label> | ||||
| <input type="text" class="form-control" name="InvoiceData.Identifier" value="12345678" required> | |||||
| <input type="text" | |||||
| class="form-control" | |||||
| name="InvoiceData.Identifier" | |||||
| value="@(string.IsNullOrWhiteSpace(Model.CompanyTaxId) ? "" : Model.CompanyTaxId)" | |||||
| required> | |||||
| <small class="text-muted">8位數字</small> | <small class="text-muted">8位數字</small> | ||||
| </div> | </div> | ||||
| <div class="col-md-6"> | <div class="col-md-6"> | ||||
| <label class="form-label">發票交易日期 (TransDateTime) *</label> | <label class="form-label">發票交易日期 (TransDateTime) *</label> | ||||
| <input type="text" class="form-control" name="InvoiceData.TransDateTime" value="2024-01-15 14:30:00" required> | |||||
| <input type="text" | |||||
| class="form-control" | |||||
| name="InvoiceData.TransDateTime" | |||||
| value="@Model.CurrentTransDateTime" | |||||
| required> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -42,26 +51,26 @@ | |||||
| </div> | </div> | ||||
| <div class="col-md-6"> | <div class="col-md-6"> | ||||
| <label class="form-label">設備代號 (DeviceID) *</label> | <label class="form-label">設備代號 (DeviceID) *</label> | ||||
| <input type="text" class="form-control" name="InvoiceData.DeviceID" value="DEVICE001" required> | |||||
| <input type="text" class="form-control" name="InvoiceData.DeviceID" value="NtuInvoiceGateway" required> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div class="row mb-3"> | <div class="row mb-3"> | ||||
| <div class="col-md-6"> | <div class="col-md-6"> | ||||
| <label class="form-label">停車場站代號 (LocationID) *</label> | <label class="form-label">停車場站代號 (LocationID) *</label> | ||||
| <input type="text" class="form-control" name="InvoiceData.LocationID" value="A" maxlength="1" required> | |||||
| <input type="text" class="form-control" name="InvoiceData.LocationID" value="1" maxlength="1" required> | |||||
| <small class="text-muted">1個字元</small> | <small class="text-muted">1個字元</small> | ||||
| </div> | </div> | ||||
| <div class="col-md-6"> | <div class="col-md-6"> | ||||
| <label class="form-label">車牌號碼 (CarPlateNum) *</label> | <label class="form-label">車牌號碼 (CarPlateNum) *</label> | ||||
| <input type="text" class="form-control" name="InvoiceData.CarPlateNum" value="ABC1234" required> | |||||
| <input type="text" class="form-control" name="InvoiceData.CarPlateNum" value="9M1234" required> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div class="row mb-3"> | <div class="row mb-3"> | ||||
| <div class="col-md-6"> | <div class="col-md-6"> | ||||
| <label class="form-label">訂單編號 (OrderID) *</label> | <label class="form-label">訂單編號 (OrderID) *</label> | ||||
| <input type="text" class="form-control" name="InvoiceData.OrderID" value="ORDER20240115001" required> | |||||
| <input type="text" class="form-control" name="InvoiceData.OrderID" value="@Model.CurrentOrderNo" required> | |||||
| </div> | </div> | ||||
| <div class="col-md-6"> | <div class="col-md-6"> | ||||
| <label class="form-label">稅別 (TaxType) *</label> | <label class="form-label">稅別 (TaxType) *</label> | ||||
| @@ -114,6 +123,21 @@ | |||||
| </ul> | </ul> | ||||
| </div> | </div> | ||||
| <hr class="my-4"> | |||||
| <div class="card border-primary"> | |||||
| <div class="card-header bg-primary text-white"> | |||||
| <h5>JSON POST 測試(符合規格要求)</h5> | |||||
| </div> | |||||
| <div class="card-body"> | |||||
| <p class="text-success"><strong>✓ 此方式符合英特拉規格:Method: POST, 資料格式: JSON</strong></p> | |||||
| <button type="button" class="btn btn-primary btn-lg w-100" onclick="testJsonPost()"> | |||||
| 發送 JSON POST 請求到發票頁面 | |||||
| </button> | |||||
| <div id="jsonResult" class="mt-3"></div> | |||||
| </div> | |||||
| </div> | |||||
| <hr> | <hr> | ||||
| <div class="card bg-light"> | <div class="card bg-light"> | ||||
| @@ -127,5 +151,55 @@ | |||||
| </div> | </div> | ||||
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | ||||
| <script> | |||||
| async function testJsonPost() { | |||||
| const resultDiv = document.getElementById('jsonResult'); | |||||
| resultDiv.innerHTML = '<div class="alert alert-info">發送中...</div>'; | |||||
| const form = document.querySelector('form'); | |||||
| if (!form) { | |||||
| resultDiv.innerHTML = '<div class="alert alert-danger">找不到表單元素</div>'; | |||||
| return; | |||||
| } | |||||
| const formData = new FormData(form); | |||||
| const jsonData = { | |||||
| identifier: formData.get('InvoiceData.Identifier')?.toString() ?? '', | |||||
| transDateTime: formData.get('InvoiceData.TransDateTime')?.toString() ?? '', | |||||
| transAmount: formData.get('InvoiceData.TransAmount')?.toString() ?? '', | |||||
| deviceID: formData.get('InvoiceData.DeviceID')?.toString() ?? '', | |||||
| email: formData.get('InvoiceData.Email')?.toString() ?? '', | |||||
| carrierID: formData.get('InvoiceData.CarrierID')?.toString() ?? '', | |||||
| locationID: formData.get('InvoiceData.LocationID')?.toString() ?? '', | |||||
| carPlateNum: formData.get('InvoiceData.CarPlateNum')?.toString() ?? '', | |||||
| orderID: formData.get('InvoiceData.OrderID')?.toString() ?? '', | |||||
| buyerIdentifier: formData.get('InvoiceData.BuyerIdentifier')?.toString() ?? '', | |||||
| loveCode: '', | |||||
| taxType: formData.get('InvoiceData.TaxType')?.toString() ?? '' | |||||
| }; | |||||
| try { | |||||
| const response = await fetch('/Invoice', { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json' | |||||
| }, | |||||
| body: JSON.stringify(jsonData) | |||||
| }); | |||||
| if (response.ok) { | |||||
| // JSON POST 成功,直接顯示返回的 HTML 頁面 | |||||
| const html = await response.text(); | |||||
| document.open(); | |||||
| document.write(html); | |||||
| document.close(); | |||||
| } else { | |||||
| resultDiv.innerHTML = `<div class="alert alert-danger">請求失敗: ${response.status}</div>`; | |||||
| } | |||||
| } catch (error) { | |||||
| resultDiv.innerHTML = `<div class="alert alert-danger">發生錯誤: ${error.message}</div>`; | |||||
| } | |||||
| } | |||||
| </script> | |||||
| </body> | </body> | ||||
| </html> | </html> | ||||
| @@ -1,9 +1,23 @@ | |||||
| using System; | |||||
| using Altob.NtuInvoiceGateway.Models; | |||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||
| using Microsoft.Extensions.Options; | |||||
| namespace Altob.NtuInvoiceGateway.Pages; | namespace Altob.NtuInvoiceGateway.Pages; | ||||
| public class TestPostInvoiceModel : PageModel | public class TestPostInvoiceModel : PageModel | ||||
| { | { | ||||
| public string CompanyTaxId { get; } | |||||
| public string CurrentTransDateTime { get; } | |||||
| public string CurrentOrderNo { get; } | |||||
| public TestPostInvoiceModel(IOptions<CompanyInfo> companyInfo) | |||||
| { | |||||
| CompanyTaxId = companyInfo.Value.TaxId; | |||||
| CurrentTransDateTime = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); | |||||
| CurrentOrderNo = DateTime.Now.ToString("ORDERyyyyMMddHHmmssfff"); | |||||
| } | |||||
| public void OnGet() | public void OnGet() | ||||
| { | { | ||||
| } | } | ||||
| @@ -7,6 +7,8 @@ builder.Services.AddHttpClient(); | |||||
| // Configure company info | // Configure company info | ||||
| builder.Services.Configure<Altob.NtuInvoiceGateway.Models.CompanyInfo>( | builder.Services.Configure<Altob.NtuInvoiceGateway.Models.CompanyInfo>( | ||||
| builder.Configuration.GetSection("CompanyInfo")); | builder.Configuration.GetSection("CompanyInfo")); | ||||
| builder.Services.Configure<Altob.NtuInvoiceGateway.Models.InvoiceApiOptions>( | |||||
| builder.Configuration.GetSection("InvoiceApi")); | |||||
| var app = builder.Build(); | var app = builder.Build(); | ||||
| @@ -28,4 +30,4 @@ app.MapStaticAssets(); | |||||
| app.MapRazorPages() | app.MapRazorPages() | ||||
| .WithStaticAssets(); | .WithStaticAssets(); | ||||
| app.Run(); | |||||
| app.Run(); | |||||
| @@ -5,5 +5,8 @@ | |||||
| "Default": "Information", | "Default": "Information", | ||||
| "Microsoft.AspNetCore": "Warning" | "Microsoft.AspNetCore": "Warning" | ||||
| } | } | ||||
| }, | |||||
| "InvoiceApi": { | |||||
| "Endpoint": "http://192.168.110.72:22055/api/Intella/invoiceInfo" | |||||
| } | } | ||||
| } | } | ||||
| @@ -9,5 +9,8 @@ | |||||
| "CompanyInfo": { | "CompanyInfo": { | ||||
| "Name": "國立台灣大學臨時停車場", | "Name": "國立台灣大學臨時停車場", | ||||
| "TaxId": "18384226" | "TaxId": "18384226" | ||||
| }, | |||||
| "InvoiceApi": { | |||||
| "Endpoint": "http://192.168.110.72:22055/api/Intella/invoiceInfo" | |||||
| } | } | ||||
| } | } | ||||