diff --git a/PaymentBridge.cs b/PaymentBridge.cs index 780e7b8..1542df5 100644 --- a/PaymentBridge.cs +++ b/PaymentBridge.cs @@ -110,7 +110,9 @@ namespace PaymentBridge var addr = parts[1]; if (addr.Contains(":")) { - GW_PORT = addr.Split(':')[1]; + string port = addr.Split(':')[1]; + if (port == "8080" || port == "7779") continue; // 웹UI/Bridge 포트 제외 + GW_PORT = port; Log("GW Port found: " + GW_PORT); break; } @@ -220,6 +222,12 @@ namespace PaymentBridge var data = json.Deserialize(body); responseString = ProcessCashReceipt("B2", data); } + else if (path == "/api/print" && request.HttpMethod == "POST") + { + var body = ReadBody(request); + var data = json.Deserialize(body); + responseString = ProcessPrint(data); + } else { responseString = json.Serialize(new { ok = false, error = "Unknown endpoint" }); @@ -419,6 +427,89 @@ namespace PaymentBridge Console.WriteLine(message); } + static string ProcessPrint(dynamic data) + { + // POS에서 받은 영수증 데이터를 DLL 경유로 인쇄 (MMF → AllthatpayClient → 프린터) + // data: { text: "영수증텍스트", qr_url: "https://...", qr_points: 100 } + + try + { + string text = ""; + string qrUrl = ""; + int qrPoints = 0; + + try { text = data["text"] ?? ""; } catch { } + try { qrUrl = data["qr_url"] ?? ""; } catch { } + try { qrPoints = Convert.ToInt32(data["qr_points"]); } catch { } + + if (string.IsNullOrEmpty(text)) + return json.Serialize(new { ok = false, error = "text is empty" }); + + // QR 적립 안내 텍스트 추가 + if (!string.IsNullOrEmpty(qrUrl) && qrPoints > 0) + { + text += "\n------------------------------------------------\n"; + text += " ★ 마일리지 적립 ★\n"; + text += " QR 스캔하고 " + qrPoints.ToString("N0") + "P 받으세요!\n"; + text += " (유효기간: 30일)\n"; + text += "------------------------------------------------\n"; + } + + // EUC-KR 인코딩 + Encoding eucKr = Encoding.GetEncoding("euc-kr"); + byte[] textBytes = eucKr.GetBytes(text); + + // 패킷 조립: text + FS×4 + ATQR_URL (DLL 방식 — PRINTSTART/END 불필요) + var packet = new System.Collections.Generic.List(); + packet.AddRange(textBytes); + + // QR 코드 추가 (ATQR_ 프로토콜) + if (!string.IsNullOrEmpty(qrUrl)) + { + byte fs = 0x1C; + packet.Add(fs); packet.Add(fs); packet.Add(fs); packet.Add(fs); + packet.AddRange(Encoding.ASCII.GetBytes("ATQR_" + qrUrl)); + } + + byte[] buf = packet.ToArray(); + + // DLL 미연결 시 재연결 + if (!isConnected) + { + FindGWPort(); + ConnectDLL(); + } + + // DLL 경유 인쇄 (iCmd=2) + int result = AllThatPayCatReqMC(2, buf, buf.Length); + + if (result != 0) + { + Log("Print OK (DLL): " + text.Length + " chars, QR=" + (!string.IsNullOrEmpty(qrUrl))); + return json.Serialize(new { ok = true, method = "dll", has_qr = !string.IsNullOrEmpty(qrUrl), bytes_sent = buf.Length }); + } + + // DLL 실패 → 재연결 후 1회 재시도 + Log("Print DLL failed, reconnecting..."); + FindGWPort(); + ConnectDLL(); + + result = AllThatPayCatReqMC(2, buf, buf.Length); + if (result != 0) + { + Log("Print OK (DLL retry): " + text.Length + " chars, QR=" + (!string.IsNullOrEmpty(qrUrl))); + return json.Serialize(new { ok = true, method = "dll_retry", has_qr = !string.IsNullOrEmpty(qrUrl), bytes_sent = buf.Length }); + } + + return json.Serialize(new { ok = false, error = "DLL CatReqMC(2) returned 0" }); + } + catch (Exception ex) + { + Log("ProcessPrint error: " + ex.Message); + return json.Serialize(new { ok = false, error = ex.Message }); + } + } + static void Cleanup() { try diff --git a/PaymentBridge.exe b/PaymentBridge.exe index 9db5958..ad6d6d3 100644 Binary files a/PaymentBridge.exe and b/PaymentBridge.exe differ diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..38b4cea --- /dev/null +++ b/build.bat @@ -0,0 +1,8 @@ +@echo off +cd /d "%~dp0" +C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /nologo /platform:x86 /target:winexe /win32icon:shc.ico /reference:System.dll /reference:System.Drawing.dll /reference:System.Windows.Forms.dll /reference:System.Web.Extensions.dll /out:PaymentBridge.exe PaymentBridge.cs +if %errorlevel%==0 ( + echo BUILD SUCCESS +) else ( + echo BUILD FAILED +) diff --git a/test_print_dll.ps1 b/test_print_dll.ps1 new file mode 100644 index 0000000..49551be --- /dev/null +++ b/test_print_dll.ps1 @@ -0,0 +1,105 @@ +# 32비트 PowerShell에서 AllthatModule.dll iCmd=2 영수증출력 테스트 +# 실행: C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File test_print_dll.ps1 + +Add-Type -TypeDefinition @" +using System; +using System.Runtime.InteropServices; + +public class AllThatPay { + private const string DLL = @"C:\Program Files (x86)\AllthatpayClient\AllthatModule.dll"; + + [DllImport(DLL, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern int AllThatPayOpenModule(string gwIp, string gwPort, string atpIp, string atpPort, string bizNo, string ediType, int optFlag); + + [DllImport(DLL, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern int AllThatPayCloseModule(); + + [DllImport(DLL, CallingConvention = CallingConvention.StdCall)] + public static extern int AllThatPayCatReqMC(int iCmd, byte[] pInBuf, int iInLength); + + [DllImport(DLL, CallingConvention = CallingConvention.StdCall)] + public static extern int AllThatPayRetCon(); + + [DllImport(DLL, CallingConvention = CallingConvention.StdCall)] + public static extern IntPtr AllThatPayRetData(int iPosition); +} +"@ + +# --- GW 포트 자동 탐지 --- +$gwPort = $null +$proc = Get-Process -Name "AllthatpayClient" -ErrorAction SilentlyContinue | Select-Object -First 1 +if ($proc) { + $netstat = netstat -ano 2>$null + foreach ($line in $netstat) { + if ($line -match "LISTENING" -and $line -match "\s$($proc.Id)\s*$") { + if ($line -match ":(\d+)\s") { + $port = [int]$Matches[1] + if ($port -ne 8080 -and $port -ne 7779) { + $gwPort = $port.ToString() + break + } + } + } + } +} + +if (-not $gwPort) { + Write-Host "[ERROR] GW port not found" -ForegroundColor Red + exit 1 +} +Write-Host "[INFO] GW Port: $gwPort" -ForegroundColor Cyan + +# --- DLL 연결 --- +$r = [AllThatPay]::AllThatPayOpenModule("127.0.0.1", $gwPort, "", "", "8134500294", "P01", 0) +Write-Host "[INFO] OpenModule: $r (1=OK)" -ForegroundColor $(if ($r -eq 1) {"Green"} else {"Red"}) + +if ($r -ne 1) { + Write-Host "[ERROR] OpenModule failed" -ForegroundColor Red + exit 1 +} + +# --- 영수증 텍스트 (EUC-KR) --- +$eucKr = [System.Text.Encoding]::GetEncoding("euc-kr") + +$text = "================================================`n" +$text += " [ 청 춘 약 국 ]`n" +$text += " 경기 양주시 양주역로 7-3 1층`n" +$text += " TEL: 033-481-7390`n" +$text += "================================================`n" +$text += "`n" +$text += " ** DLL iCmd=2 인쇄 테스트 **`n" +$text += " 시간: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`n" +$text += "`n" +$text += "------------------------------------------------`n" +$text += " ★ 마일리지 적립 ★`n" +$text += " QR 스캔하고 100P 받으세요!`n" +$text += " (유효기간: 30일)`n" +$text += "------------------------------------------------`n" +$text += "`n`n" + +$textBytes = $eucKr.GetBytes($text) + +# --- QR 코드 추가: FS×4 + ATQR_URL (명세서 CMD 2 규격) --- +$qrUrl = "https://shc.pharmq.kr/claim?t=test_dll_print" +[byte]$fs = 0x1C +$qrTag = $eucKr.GetBytes("ATQR_" + $qrUrl) + +$buf = New-Object System.Collections.Generic.List[byte] +$buf.AddRange($textBytes) +$buf.Add($fs); $buf.Add($fs); $buf.Add($fs); $buf.Add($fs) +$buf.AddRange($qrTag) +$packet = $buf.ToArray() + +Write-Host "[INFO] iCmd=2 send ($($packet.Length) bytes, text=$($textBytes.Length) + QR=$($qrTag.Length))..." -ForegroundColor Yellow + +# --- iCmd=2 영수증출력 --- +try { + $pr = [AllThatPay]::AllThatPayCatReqMC(2, $packet, $packet.Length) + Write-Host "[INFO] CatReqMC(2): $pr" -ForegroundColor $(if ($pr -ne 0) {"Green"} else {"Red"}) +} catch { + Write-Host "[ERROR] CatReqMC exception: $_" -ForegroundColor Red +} + +# --- 정리 --- +try { [AllThatPay]::AllThatPayCloseModule() | Out-Null } catch {} +Write-Host "[DONE]" -ForegroundColor Cyan