added route for taking preauthorization payment
This commit is contained in:
parent
89dfa28e6f
commit
a4885be458
@ -21,8 +21,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
customLayout = "2006-01-02 15:04:05 -0700"
|
customLayout = "2006-01-02 15:04:05 -0700"
|
||||||
transactionUrl = "http://127.0.0.1:18181/start-transaction/"
|
takePreauthorizationUrl = "http://127.0.0.1:18181/start-transaction/"
|
||||||
|
takePaymentUrl = "http://127.0.0.1:18181/start-and-confirm-transaction/"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
@ -42,11 +43,12 @@ func NewApp(dispPort *serial.Port, lockType, encoderAddress string, isPayment bo
|
|||||||
func (app *App) RegisterRoutes(mux *http.ServeMux) {
|
func (app *App) RegisterRoutes(mux *http.ServeMux) {
|
||||||
mux.HandleFunc("/issuedoorcard", app.issueDoorCard)
|
mux.HandleFunc("/issuedoorcard", app.issueDoorCard)
|
||||||
mux.HandleFunc("/printroomticket", app.printRoomTicket)
|
mux.HandleFunc("/printroomticket", app.printRoomTicket)
|
||||||
mux.HandleFunc("/starttransaction", app.startTransaction)
|
mux.HandleFunc("/takepreauth", app.takePreauthorization)
|
||||||
|
mux.HandleFunc("/takepayment", app.takePayment)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) startTransaction(w http.ResponseWriter, r *http.Request) {
|
func (app *App) takePreauthorization(w http.ResponseWriter, r *http.Request) {
|
||||||
const op = logging.Op("startTransaction")
|
const op = logging.Op("takePreauthorization")
|
||||||
var (
|
var (
|
||||||
theResponse cmstypes.ResponseRec
|
theResponse cmstypes.ResponseRec
|
||||||
cardholderReceipt string
|
cardholderReceipt string
|
||||||
@ -73,7 +75,7 @@ func (app *App) startTransaction(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("startTransaction called")
|
log.Println("takePreauthorization called")
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Method not allowed; use POST")
|
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Method not allowed; use POST")
|
||||||
writeTransactionResult(w, http.StatusMethodNotAllowed, theResponse)
|
writeTransactionResult(w, http.StatusMethodNotAllowed, theResponse)
|
||||||
@ -95,10 +97,10 @@ func (app *App) startTransaction(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeTransactionResult(w, http.StatusBadRequest, theResponse)
|
writeTransactionResult(w, http.StatusBadRequest, theResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("Start trnasaction payload: Amount=%s, Type=%s", theRequest.AmountMinorUnits, theRequest.TransactionType)
|
log.Printf("Transaction payload: Amount=%s, Type=%s", theRequest.AmountMinorUnits, theRequest.TransactionType)
|
||||||
|
|
||||||
client := &http.Client{Timeout: 300 * time.Second}
|
client := &http.Client{Timeout: 300 * time.Second}
|
||||||
response, err := client.Post(transactionUrl, "text/xml", bytes.NewBuffer(body))
|
response, err := client.Post(takePreauthorizationUrl, "text/xml", bytes.NewBuffer(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error(serviceName, err.Error(), "Payment processing error", string(op), "", "", 0)
|
logging.Error(serviceName, err.Error(), "Payment processing error", string(op), "", "", 0)
|
||||||
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "No response from payment processor")
|
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "No response from payment processor")
|
||||||
@ -140,7 +142,106 @@ func (app *App) startTransaction(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Errorf("PrintCardholderReceipt error: %v", err)
|
log.Errorf("PrintCardholderReceipt error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
theResponse.Data = payment.BuildRedirectURL(result)
|
theResponse.Data = payment.BuildPreauthRedirectURL(result)
|
||||||
|
writeTransactionResult(w, http.StatusOK, theResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) takePayment(w http.ResponseWriter, r *http.Request) {
|
||||||
|
const op = logging.Op("takePayment")
|
||||||
|
var (
|
||||||
|
theResponse cmstypes.ResponseRec
|
||||||
|
cardholderReceipt string
|
||||||
|
theRequest cmstypes.TransactionRec
|
||||||
|
trResult payment.TransactionResultXML
|
||||||
|
)
|
||||||
|
|
||||||
|
theResponse.Status.Code = http.StatusInternalServerError
|
||||||
|
theResponse.Status.Message = "500 Internal server error"
|
||||||
|
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
if !app.isPayment {
|
||||||
|
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Payment processing is disabled")
|
||||||
|
writeTransactionResult(w, http.StatusServiceUnavailable, theResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodOptions {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("takePayment called")
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Method not allowed; use POST")
|
||||||
|
writeTransactionResult(w, http.StatusMethodNotAllowed, theResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
if ct := r.Header.Get("Content-Type"); ct != "text/xml" {
|
||||||
|
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Content-Type must be text/xml")
|
||||||
|
writeTransactionResult(w, http.StatusUnsupportedMediaType, theResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
err := xml.Unmarshal(body, &theRequest)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error(serviceName, err.Error(), "ReadXML", string(op), "", "", 0)
|
||||||
|
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Invalid XML payload")
|
||||||
|
writeTransactionResult(w, http.StatusBadRequest, theResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Transaction payload: Amount=%s, Type=%s", theRequest.AmountMinorUnits, theRequest.TransactionType)
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 300 * time.Second}
|
||||||
|
response, err := client.Post(takePaymentUrl, "text/xml", bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
logging.Error(serviceName, err.Error(), "Payment processing error", string(op), "", "", 0)
|
||||||
|
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "No response from payment processor")
|
||||||
|
writeTransactionResult(w, http.StatusBadGateway, theResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
body, err = io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error(serviceName, err.Error(), "Read response body error", string(op), "", "", 0)
|
||||||
|
theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Failed to read response body")
|
||||||
|
writeTransactionResult(w, http.StatusInternalServerError, theResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := trResult.ParseTransactionResult(body); err != nil {
|
||||||
|
logging.Error(serviceName, err.Error(), "Parse transaction result error", string(op), "", "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose JSON from responseEntries
|
||||||
|
result := make(map[string]string)
|
||||||
|
for _, e := range trResult.Entries {
|
||||||
|
switch e.Key {
|
||||||
|
case payment.ReceiptData, payment.ReceiptDataMerchant:
|
||||||
|
// ignore these
|
||||||
|
case payment.ReceiptDataCardholder:
|
||||||
|
cardholderReceipt = e.Value
|
||||||
|
case payment.TransactionResult:
|
||||||
|
theResponse.Status.Message = e.Value
|
||||||
|
theResponse.Status.Code = http.StatusOK
|
||||||
|
result[e.Key] = e.Value
|
||||||
|
default:
|
||||||
|
result[e.Key] = e.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := printer.PrintCardholderReceipt(cardholderReceipt); err != nil {
|
||||||
|
log.Errorf("PrintCardholderReceipt error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
theResponse.Data = payment.BuildPaymentRedirectURL(result)
|
||||||
writeTransactionResult(w, http.StatusOK, theResponse)
|
writeTransactionResult(w, http.StatusOK, theResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@ -23,7 +23,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
buildVersion = "1.0.25"
|
buildVersion = "1.0.26"
|
||||||
serviceName = "hardlink"
|
serviceName = "hardlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -250,7 +250,31 @@ func nullableFloatArg(nf sql.NullFloat64) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildRedirectURL builds the redirect URL to send the guest to after payment.
|
// BuildRedirectURL builds the redirect URL to send the guest to after payment.
|
||||||
func BuildRedirectURL(result map[string]string) string {
|
func BuildPaymentRedirectURL(result map[string]string) string {
|
||||||
|
res := result[TransactionResult]
|
||||||
|
|
||||||
|
// Transaction approved?
|
||||||
|
if strings.EqualFold(res, ResultApproved) {
|
||||||
|
// Transaction confirmed?
|
||||||
|
if strings.EqualFold(result[ConfirmResult], ResultApproved) {
|
||||||
|
log.WithField(LogResult, result[ConfirmResult]).
|
||||||
|
Info("Transaction approved and confirmed")
|
||||||
|
|
||||||
|
return buildSuccessURL(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not confirmed
|
||||||
|
log.WithFields(log.Fields{LogFieldError: result[ConfirmResult], LogFieldDescription: result[ConfirmErrors]}).
|
||||||
|
Error("Transaction approved but not confirmed")
|
||||||
|
|
||||||
|
return BuildFailureURL(result[ConfirmResult], result[ConfirmErrors])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not approved
|
||||||
|
return BuildFailureURL(res, result[Errors])
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildPreauthRedirectURL(result map[string]string) string {
|
||||||
res := result[TransactionResult]
|
res := result[TransactionResult]
|
||||||
tType := result[TransactionType]
|
tType := result[TransactionType]
|
||||||
|
|
||||||
@ -267,18 +291,10 @@ func BuildRedirectURL(result map[string]string) string {
|
|||||||
// Transaction type Sale?
|
// Transaction type Sale?
|
||||||
case strings.EqualFold(tType, SaleTransactionType):
|
case strings.EqualFold(tType, SaleTransactionType):
|
||||||
// Transaction confirmed?
|
// Transaction confirmed?
|
||||||
if strings.EqualFold(result[ConfirmResult], ResultApproved) {
|
log.WithField(LogResult, result[ConfirmResult]).
|
||||||
log.WithField(LogResult, result[ConfirmResult]).
|
Info("Amount preauthorized successfully")
|
||||||
Info("Transaction approved and confirmed")
|
|
||||||
|
|
||||||
return buildSuccessURL(result)
|
return buildSuccessURL(result)
|
||||||
}
|
|
||||||
|
|
||||||
// Not confirmed
|
|
||||||
log.WithFields(log.Fields{LogFieldError: result[ConfirmResult], LogFieldDescription: result[ConfirmErrors]}).
|
|
||||||
Error("Transaction approved but not confirmed")
|
|
||||||
|
|
||||||
return BuildFailureURL(result[ConfirmResult], result[ConfirmErrors])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
builtVersion is a const in main.go
|
builtVersion is a const in main.go
|
||||||
|
|
||||||
|
#### 1.0.26 - 10 December 2025
|
||||||
|
added route for taking preauthorization payment
|
||||||
|
|
||||||
#### 1.0.25 - 08 December 2025
|
#### 1.0.25 - 08 December 2025
|
||||||
return masked card number and expiry date in the payment success URL
|
return masked card number and expiry date in the payment success URL
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user