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:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Build outputs
|
||||
*.exe
|
||||
*.pdb
|
||||
*.dll
|
||||
|
||||
# But keep source
|
||||
!*.cs
|
||||
|
||||
# IDE
|
||||
.vs/
|
||||
*.user
|
||||
420
PaymentBridge.cs
Normal file
420
PaymentBridge.cs
Normal 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 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
38
PaymentStop.cs
Normal file
38
PaymentStop.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
class PaymentStop
|
||||
{
|
||||
private const string POS_DLL = @"C:\Program Files (x86)\AllthatpayClient\PosToCatReq.dll";
|
||||
|
||||
[DllImport(POS_DLL, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern int ReqStop();
|
||||
|
||||
[DllImport(POS_DLL, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern int ReqStop_TCP(string gwIp, string gwPort);
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("=== Payment Stop ===");
|
||||
|
||||
try
|
||||
{
|
||||
int r1 = ReqStop();
|
||||
Console.WriteLine("ReqStop: " + r1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("ReqStop error: " + ex.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int r2 = ReqStop_TCP("127.0.0.1", "49884");
|
||||
Console.WriteLine("ReqStop_TCP: " + r2);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("ReqStop_TCP error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
payment_bridge.log
Normal file
45
payment_bridge.log
Normal file
@@ -0,0 +1,45 @@
|
||||
2026-03-23 23:06:26 GW Port found: 49884
|
||||
2026-03-23 23:06:26 DLL Connect: OK
|
||||
2026-03-23 23:06:26 HTTP Server started on port 7779
|
||||
2026-03-23 23:07:06 CashReceipt B1 result: 0
|
||||
2026-03-23 23:13:34 GW Port found: 49884
|
||||
2026-03-23 23:13:34 DLL Connect: OK
|
||||
2026-03-23 23:13:34 HTTP Server started on port 7779
|
||||
2026-03-23 23:14:43 GW Port found: 49884
|
||||
2026-03-23 23:14:43 DLL Connect: OK
|
||||
2026-03-23 23:14:43 HTTP Server started on port 7779
|
||||
2026-03-27 22:07:51 GW Port found: 65238
|
||||
2026-03-27 22:07:51 ConnectDLL error: 프로그램을 잘못된 형식으로 로드하려고 했습니다. (예외가 발생한 HRESULT: 0x8007000B)
|
||||
2026-03-27 22:07:51 HTTP Server started on port 7779
|
||||
2026-03-27 22:08:39 ProcessStop called
|
||||
2026-03-27 22:08:39 ProcessStop error: 프로그램을 잘못된 형식으로 로드하려고 했습니다. (예외가 발생한 HRESULT: 0x8007000B)
|
||||
2026-03-27 22:08:56 GW Port found: 65238
|
||||
2026-03-27 22:08:57 DLL Connect: OK
|
||||
2026-03-27 22:08:57 HTTP Server started on port 7779
|
||||
2026-03-27 22:12:33 GW Port found: 65238
|
||||
2026-03-27 22:12:33 DLL Connect: OK
|
||||
2026-03-27 22:12:33 HTTP Server started on port 7779
|
||||
2026-03-27 22:12:43 ProcessStop called
|
||||
2026-03-27 22:12:43 ReqStop_TCP result: 1
|
||||
2026-03-27 22:17:10 ProcessStop called
|
||||
2026-03-27 22:17:10 ReqStop_TCP result: 1
|
||||
2026-03-27 22:20:15 GW Port found: 65238
|
||||
2026-03-27 22:20:15 DLL Connect: OK
|
||||
2026-03-27 22:20:15 HTTP Server started on port 7779
|
||||
2026-03-27 22:20:24 ProcessStop called
|
||||
2026-03-27 22:20:25 ReqStop result: 1
|
||||
2026-03-27 22:20:25 ReqStop_TCP result: 1
|
||||
2026-03-27 22:20:25 Direct ESC/CAN sent to GW
|
||||
2026-03-27 22:22:53 GW Port found: 65238
|
||||
2026-03-27 22:22:54 DLL Connect: OK
|
||||
2026-03-27 22:22:54 HTTP Server started on port 7779
|
||||
2026-03-27 22:23:02 ProcessStop called
|
||||
2026-03-27 22:23:03 ReqStop result: 1
|
||||
2026-03-27 22:23:03 ReqStop_TCP result: 1
|
||||
2026-03-27 22:23:03 Direct ESC/CAN sent to GW
|
||||
2026-03-27 22:25:20 GW Port found: 65238
|
||||
2026-03-27 22:25:20 DLL Connect: OK
|
||||
2026-03-27 22:25:20 HTTP Server started on port 7779
|
||||
2026-03-27 22:56:58 GW Port found: 65238
|
||||
2026-03-27 22:56:58 DLL Connect: OK
|
||||
2026-03-27 22:56:58 HTTP Server started on port 7779
|
||||
Reference in New Issue
Block a user