Initial commit: Payment Bridge (올댓페이 C# DLL Wrapper)

- PaymentBridge.cs: HTTP API 서버 (포트 7779)
  - /api/status: 연결 상태 확인
  - /api/card/approve: 카드 승인 (D1)
  - /api/card/cancel: 카드 취소 (D2)
  - /api/card/stop: 결제 진행 중 취소 시도
  - /api/cash/receipt: 현금영수증 (B1)
  - /api/cash/cancel: 현금영수증 취소 (B2)

- PaymentStop.cs: 별도 취소 exe (테스트용)

- 사용 DLL:
  - AllthatModule.dll (카드결제)
  - PosToCatReq.dll (ReqStop)
This commit is contained in:
청춘약국
2026-03-27 23:08:29 +09:00
commit a0d9b4364a
5 changed files with 514 additions and 0 deletions

420
PaymentBridge.cs Normal file
View File

@@ -0,0 +1,420 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Net;
using System.Threading;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.Web.Script.Serialization;
namespace PaymentBridge
{
public class Program
{
// AllThatPay DLL Imports
private const string DLL_PATH = @"C:\Program Files (x86)\AllthatpayClient\AllthatModule.dll";
private const string POS_DLL_PATH = @"C:\Program Files (x86)\AllthatpayClient\PosToCatReq.dll";
[DllImport(DLL_PATH, 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_PATH, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern int AllThatPayCloseModule();
[DllImport(DLL_PATH, CallingConvention = CallingConvention.StdCall)]
public static extern int AllThatPayCatReqMC(int iCmd, byte[] pInBuf, int iInLength);
[DllImport(DLL_PATH, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr AllThatPayRetData(int iPosition);
// PosToCatReq DLL - 결제 진행 중 취소
[DllImport(POS_DLL_PATH, CallingConvention = CallingConvention.StdCall)]
public static extern int ReqStop();
[DllImport(POS_DLL_PATH, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern int ReqStop_TCP(string gwIp, string gwPort);
// 설정
private static string GW_PORT = "49884";
private static string BIZ_NO = "8134500294";
private static string EDI_TYPE = "P01";
private static int HTTP_PORT = 7779;
private static HttpListener listener;
private static NotifyIcon trayIcon;
private static bool isConnected = false;
private static JavaScriptSerializer json = new JavaScriptSerializer();
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
// 트레이 아이콘 설정
trayIcon = new NotifyIcon();
trayIcon.Text = "Payment Bridge";
// 커스텀 아이콘 로드
string iconPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "shc.ico");
if (File.Exists(iconPath))
trayIcon.Icon = new Icon(iconPath);
else
trayIcon.Icon = SystemIcons.Application;
trayIcon.Visible = true;
// 컨텍스트 메뉴
var menu = new ContextMenu();
menu.MenuItems.Add(new MenuItem("상태: 시작 중..."));
menu.MenuItems.Add(new MenuItem("-"));
var exitItem = new MenuItem("종료");
exitItem.Click += delegate { Cleanup(); Application.Exit(); };
menu.MenuItems.Add(exitItem);
trayIcon.ContextMenu = menu;
// GW 포트 자동 탐지
FindGWPort();
// HTTP 서버 시작
Thread serverThread = new Thread(StartHttpServer);
serverThread.IsBackground = true;
serverThread.Start();
// DLL 연결
ConnectDLL();
Application.Run();
}
static void FindGWPort()
{
try
{
var psi = new System.Diagnostics.ProcessStartInfo("netstat", "-ano");
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
var proc = System.Diagnostics.Process.Start(psi);
string output = proc.StandardOutput.ReadToEnd();
// AllthatpayClient PID 찾기
foreach (var p in System.Diagnostics.Process.GetProcessesByName("AllthatpayClient"))
{
foreach (var line in output.Split('\n'))
{
if (line.Contains("LISTENING") && line.Contains(p.Id.ToString()))
{
// 포트 추출
var parts = line.Trim().Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2)
{
var addr = parts[1];
if (addr.Contains(":"))
{
GW_PORT = addr.Split(':')[1];
Log("GW Port found: " + GW_PORT);
break;
}
}
}
}
}
}
catch (Exception ex)
{
Log("FindGWPort error: " + ex.Message);
}
}
static void ConnectDLL()
{
try
{
int result = AllThatPayOpenModule("127.0.0.1", GW_PORT, "", "", BIZ_NO, EDI_TYPE, 0); // 0=동기모드
isConnected = (result == 1);
UpdateStatus();
Log("DLL Connect: " + (isConnected ? "OK" : "FAIL"));
}
catch (Exception ex)
{
Log("ConnectDLL error: " + ex.Message);
isConnected = false;
}
}
static void UpdateStatus()
{
if (trayIcon.ContextMenu != null && trayIcon.ContextMenu.MenuItems.Count > 0)
{
string status = isConnected ? "연결됨 ✓" : "연결 안됨 ✗";
trayIcon.ContextMenu.MenuItems[0].Text = "상태: " + status + " (port:" + HTTP_PORT + ")";
trayIcon.Text = "Payment Bridge - " + status;
}
}
static void StartHttpServer()
{
try
{
listener = new HttpListener();
listener.Prefixes.Add("http://localhost:" + HTTP_PORT + "/");
listener.Prefixes.Add("http://127.0.0.1:" + HTTP_PORT + "/");
listener.Start();
Log("HTTP Server started on port " + HTTP_PORT);
while (true)
{
var context = listener.GetContext();
ThreadPool.QueueUserWorkItem(HandleRequest, context);
}
}
catch (Exception ex)
{
Log("HTTP Server error: " + ex.Message);
}
}
static void HandleRequest(object state)
{
var context = (HttpListenerContext)state;
var request = context.Request;
var response = context.Response;
string responseString = "";
try
{
string path = request.Url.AbsolutePath.ToLower();
if (path == "/api/status")
{
responseString = json.Serialize(new { ok = true, connected = isConnected, port = GW_PORT });
}
else if (path == "/api/card/approve" && request.HttpMethod == "POST")
{
var body = ReadBody(request);
var data = json.Deserialize<dynamic>(body);
responseString = ProcessCard("D1", data);
}
else if (path == "/api/card/cancel" && request.HttpMethod == "POST")
{
var body = ReadBody(request);
var data = json.Deserialize<dynamic>(body);
responseString = ProcessCard("D2", data);
}
else if (path == "/api/card/stop" && request.HttpMethod == "POST")
{
// 결제 진행 중 취소
responseString = ProcessStop();
}
else if (path == "/api/cash/receipt" && request.HttpMethod == "POST")
{
var body = ReadBody(request);
var data = json.Deserialize<dynamic>(body);
responseString = ProcessCashReceipt("B1", data);
}
else if (path == "/api/cash/cancel" && request.HttpMethod == "POST")
{
var body = ReadBody(request);
var data = json.Deserialize<dynamic>(body);
responseString = ProcessCashReceipt("B2", data);
}
else
{
responseString = json.Serialize(new { ok = false, error = "Unknown endpoint" });
}
}
catch (Exception ex)
{
responseString = json.Serialize(new { ok = false, error = ex.Message });
}
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentType = "application/json; charset=utf-8";
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
response.Close();
}
static string ReadBody(HttpListenerRequest request)
{
using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
{
return reader.ReadToEnd();
}
}
static string ProcessCard(string cmd, dynamic data)
{
// D1: 카드승인, D2: 카드취소
int amount = 0;
int tax = 0;
string installment = "00";
try { amount = Convert.ToInt32(data["amount"]); } catch { }
try { tax = Convert.ToInt32(data["tax"]); } catch { }
try { installment = data["installment"] ?? "00"; } catch { }
// 전문 조립
char fs = (char)0x1C;
string packet = cmd + fs + installment + fs + "0" + fs + amount + fs + tax + fs + fs + fs + fs + fs + fs + fs;
byte[] buf = Encoding.GetEncoding("euc-kr").GetBytes(packet);
// iCmd=3 for D1/D2
int result = AllThatPayCatReqMC(3, buf, buf.Length);
if (result != 0)
{
Thread.Sleep(500);
string retCode = GetRetData(2);
string retMsg = GetRetData(3);
string approvalNo = GetRetData(6);
return json.Serialize(new {
ok = retCode == "0000",
code = retCode,
message = retMsg,
approvalNo = approvalNo,
cmd = cmd
});
}
return json.Serialize(new { ok = false, error = "DLL returned 0", cmd = cmd });
}
static string ProcessCashReceipt(string cmd, dynamic data)
{
// B1: 현금영수증 발행, B2: 취소
int amount = 0;
int tax = 0;
string phone = "";
string traderType = "00"; // 00=소비자, 01=사업자
try { amount = Convert.ToInt32(data["amount"]); } catch { }
try { tax = Convert.ToInt32(data["tax"]); } catch { }
try { phone = data["phone"] ?? ""; } catch { }
try { traderType = data["traderType"] ?? "00"; } catch { }
int supplyAmt = (int)Math.Floor(amount / 1.1);
int vatAmt = amount - supplyAmt;
// 전문 조립
char fs = (char)0x1C;
string packet = cmd + fs + traderType + fs + supplyAmt + fs + vatAmt + fs + fs + fs + phone + fs + fs + fs + fs;
byte[] buf = Encoding.GetEncoding("euc-kr").GetBytes(packet);
// iCmd=4 for B1/B2
int result = AllThatPayCatReqMC(4, buf, buf.Length);
Log("CashReceipt " + cmd + " result: " + result);
if (result != 0)
{
Thread.Sleep(500);
string retCode = GetRetData(2);
string retMsg = GetRetData(3);
string approvalNo = GetRetData(17);
return json.Serialize(new {
ok = retCode == "0000",
code = retCode,
message = retMsg,
approvalNo = approvalNo,
cmd = cmd
});
}
return json.Serialize(new { ok = false, error = "DLL returned 0 - VAN 현금영수증 미활성화 가능성", cmd = cmd });
}
static string ProcessStop()
{
// 결제 진행 중 취소 (여러 방법 시도)
Log("ProcessStop called");
var results = new System.Collections.Generic.Dictionary<string, object>();
try
{
// 방법 1: ReqStop (직접)
try {
int r1 = ReqStop();
Log("ReqStop result: " + r1);
results["ReqStop"] = r1;
} catch (Exception ex) {
results["ReqStop"] = "error: " + ex.Message;
}
// 방법 2: ReqStop_TCP
try {
int r2 = ReqStop_TCP("127.0.0.1", GW_PORT);
Log("ReqStop_TCP result: " + r2);
results["ReqStop_TCP"] = r2;
} catch (Exception ex) {
results["ReqStop_TCP"] = "error: " + ex.Message;
}
// 방법 3: GW에 직접 취소 바이트 전송
try {
using (var client = new System.Net.Sockets.TcpClient())
{
client.Connect("127.0.0.1", int.Parse(GW_PORT));
var stream = client.GetStream();
// ESC (0x1B) + CAN (0x18) 전송
byte[] cancelBytes = new byte[] { 0x1B, 0x18 };
stream.Write(cancelBytes, 0, cancelBytes.Length);
results["DirectESC"] = "sent";
Log("Direct ESC/CAN sent to GW");
}
} catch (Exception ex) {
results["DirectESC"] = "error: " + ex.Message;
}
return json.Serialize(new { ok = true, results = results });
}
catch (Exception ex)
{
Log("ProcessStop error: " + ex.Message);
return json.Serialize(new { ok = false, error = ex.Message });
}
}
static string GetRetData(int position)
{
try
{
IntPtr ptr = AllThatPayRetData(position);
if (ptr != IntPtr.Zero)
{
return Marshal.PtrToStringAnsi(ptr) ?? "";
}
}
catch { }
return "";
}
static void Log(string message)
{
try
{
string logPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "payment_bridge.log");
File.AppendAllText(logPath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " " + message + Environment.NewLine);
}
catch { }
Console.WriteLine(message);
}
static void Cleanup()
{
try
{
if (isConnected) AllThatPayCloseModule();
if (listener != null) listener.Stop();
if (trayIcon != null) trayIcon.Visible = false;
}
catch { }
}
}
}