diff --git a/lockserver/assalockserver.go b/lockserver/assalockserver.go index c8964ca..dbd7615 100644 --- a/lockserver/assalockserver.go +++ b/lockserver/assalockserver.go @@ -10,11 +10,11 @@ import ( ) // Build key encoding request command for the Assa Abloy lock server. -func (lock *AssaLockServer) BuildCommand(encoderAddr, lockId string, checkIn, checkOut time.Time) error { +func (lock *AssaLockServer) BuildCommand(lockId string, checkIn, checkOut time.Time) error { ci := checkIn.Format("200601021504") co := checkOut.Format("200601021504") - lock.command = fmt.Sprintf("CCA;EA%s;GR%s;CO%s;CI%s;AM1;\r\n", encoderAddr, lockId, co, ci) + lock.command = fmt.Sprintf("CCA;EA%s;GR%s;CO%s;CI%s;AM1;\r\n", lock.encoderAddr, lockId, co, ci) return nil } diff --git a/lockserver/lockservercommon.go b/lockserver/lockservercommon.go index b554bae..fe4c14e 100644 --- a/lockserver/lockservercommon.go +++ b/lockserver/lockservercommon.go @@ -19,19 +19,33 @@ const ( type ( LockServer interface { + BuildCommand(lockId string, checkIn, checkOut time.Time) error LockSequence(conn net.Conn) error - BuildCommand(encoderAddr, lockId string, checkIn, checkOut time.Time) error } AssaLockServer struct { + encoderAddr string command string } OmniLockServer struct { + encoderAddr string // Encoder address for the lock server command []byte // Command to be sent to the lock server } ) +func NewLockServer(lockType, encoderAddr string, fatalError func(error)) LockServer { + switch lockType { + case AssaAbloy: + return &AssaLockServer{encoderAddr: encoderAddr} + case Omnitec: + return &OmniLockServer{encoderAddr: encoderAddr} + default: + fatalError(fmt.Errorf("unsupported LockType: %s; must be 'assaabloy' or 'omnitec'", lockType)) + return nil // This line will never be reached, but is needed to satisfy the compiler + } +} + func InitializeServerConnection(LockserverUrl string) (net.Conn, error) { const funcName = "InitializeServerConnection" // Parse the URL to extract host and port @@ -46,7 +60,7 @@ func InitializeServerConnection(LockserverUrl string) (net.Conn, error) { // Establish a TCP connection to the Visionline server conn, err := net.Dial("tcp", address) if err != nil { - return nil, fmt.Errorf("failed to connect to Visionline server: %v", err) + return nil, fmt.Errorf("failed to connect to lock server: %v", err) } return conn, nil } diff --git a/lockserver/omnilockserver.go b/lockserver/omnilockserver.go index ee6545f..2d8f541 100644 --- a/lockserver/omnilockserver.go +++ b/lockserver/omnilockserver.go @@ -13,7 +13,7 @@ import ( ) // Build key encoding request command for the Omnitec lock server. -func (lock *OmniLockServer) BuildCommand(encoderAddr, lockId string, checkIn, checkOut time.Time) error { +func (lock *OmniLockServer) BuildCommand(lockId string, checkIn, checkOut time.Time) error { const funcName = "OmniLockServer.BuildCommand" hostname, err := os.Hostname() if err != nil { @@ -36,7 +36,7 @@ func (lock *OmniLockServer) BuildCommand(encoderAddr, lockId string, checkIn, ch // Construct payload payload := fmt.Sprintf( "KR|KC%s|KTD|RN%s|%s|DT%s|G#75|GA%s|GD%s|KO0000|DA%s|TI%s|", - encoderAddr, + lock.encoderAddr, formattedLockId, hostname, dt, diff --git a/main.go b/main.go index 1422731..5fa8c11 100644 --- a/main.go +++ b/main.go @@ -53,10 +53,17 @@ type DoorCardRequest struct { type App struct { dispPort *serial.Port lockConn net.Conn - config configRec lockserver lockserver.LockServer } +func newApp(dispPort *serial.Port, lockConn net.Conn, config configRec) *App { + return &App{ + dispPort: dispPort, + lockConn: lockConn, + lockserver: lockserver.NewLockServer(config.LockType, config.EncoderAddress, fatalError), + } +} + func main() { // Load config config := readConfig() @@ -101,21 +108,7 @@ func main() { log.Infof("Connected to lock server at %s", config.LockserverUrl) // Create App and wire routes - app := &App{ - dispPort: dispHandle, - lockConn: lockConn, - config: config, - } - - switch config.LockType { - case lockserver.AssaAbloy: - app.lockserver = &lockserver.AssaLockServer{} - case lockserver.Omnitec: - app.lockserver = &lockserver.OmniLockServer{} - default: - err = fmt.Errorf("unsupported LockType: %s; must be 'assaabloy' or 'omnitec'", config.LockType) - fatalError(err) - } + app := newApp(dispHandle, lockConn, config) mux := http.NewServeMux() setUpRoutes(app, mux) @@ -238,7 +231,7 @@ func (app *App) issueDoorCard(w http.ResponseWriter, r *http.Request) { } // build lock server command - app.lockserver.BuildCommand(app.config.EncoderAddress, doorReq.RoomField, checkIn, checkOut) + app.lockserver.BuildCommand(doorReq.RoomField, checkIn, checkOut) // lock server sequence err = app.lockserver.LockSequence(app.lockConn) diff --git a/payment/creditcall.go b/payment/creditcall.go new file mode 100644 index 0000000..960fd0e --- /dev/null +++ b/payment/creditcall.go @@ -0,0 +1,42 @@ +package payment + +import ( + "fmt" + "net" + "time" +) + +// StartTransaction sends a start transaction XML to ChipDNA Server +func (pc *PaymentClient) StartTransaction(amountMinorUnits int, reference string) (string, error) { + conn, err := net.Dial("tcp", pc.Addr) + if err != nil { + return "", fmt.Errorf("failed to connect: %w", err) + } + defer conn.Close() + + // Set timeout + conn.SetDeadline(time.Now().Add(15 * time.Second)) + + // Format XML request + request := fmt.Sprintf(` + + %d + Actual + %s + Sale +`, amountMinorUnits, reference) + + _, err = conn.Write([]byte(request)) + if err != nil { + return "", fmt.Errorf("failed to write to server: %w", err) + } + + // Read response + buff := make([]byte, 4096) + n, err := conn.Read(buff) + if err != nil { + return "", fmt.Errorf("failed to read from server: %w", err) + } + + return string(buff[:n]), nil +} diff --git a/payment/paymentcommon.go b/payment/paymentcommon.go new file mode 100644 index 0000000..ee3be6a --- /dev/null +++ b/payment/paymentcommon.go @@ -0,0 +1,32 @@ +package payment + +import ( + "net" + "net/url" + "fmt" + "strings" +) + +// PaymentClient holds connection data +type PaymentClient struct { + Addr string // e.g., "127.0.0.1:1869" +} + +func InitializeConnection(paymentrUrl string) (net.Conn, error) { + const funcName = "InitializeServerConnection" + // Parse the URL to extract host and port + parsedUrl, err := url.Parse(paymentrUrl) + if err != nil { + return nil, fmt.Errorf("[%s] failed to parse LockserverUrl: %v", funcName, err) + } + + // Remove any leading/trailing slashes just in case + address := strings.Trim(parsedUrl.Host, "/") + + // Establish a TCP connection to the Visionline server + conn, err := net.Dial("tcp", address) + if err != nil { + return nil, fmt.Errorf("failed to connect to payment server: %v", err) + } + return conn, nil +} \ No newline at end of file