package handlers import ( "net/http" "strings" "time" "gitea.futuresens.co.uk/futuresens/cmstypes" "gitea.futuresens.co.uk/futuresens/hardlink/internal/creditcall" "gitea.futuresens.co.uk/futuresens/hardlink/internal/mail" "gitea.futuresens.co.uk/futuresens/hardlink/internal/printer" "gitea.futuresens.co.uk/futuresens/hardlink/internal/types" "gitea.futuresens.co.uk/futuresens/logging" log "github.com/sirupsen/logrus" ) // creditCallSaleOutcome is the transaction outcome before either wire format is applied. type creditCallSaleOutcome struct { HTTPStatus int Status cmstypes.StatusRec Payment creditcall.PaymentResult Approved bool FailureType string FailureDescription string } func (app *App) creditCallClient() *http.Client { // Each start/confirm call gets the original independent timeout. In particular, // neither a kiosk disconnect nor a whole-sale deadline bounds confirmation. return &http.Client{Timeout: 300 * time.Second, Transport: app.creditCallTransport} } func (app *App) printCreditCallReceipt(receipt string) { if app.creditCallReceipt != nil { app.creditCallReceipt(receipt) return } printer.PrintReceipt(receipt) } func (app *App) executeCreditCallSale( request cmstypes.TransactionRec, start func(*http.Client) (creditcall.TransactionResultXML, error), ) creditCallSaleOutcome { const op = logging.Op("takePayment") outcome := creditCallSaleOutcome{ HTTPStatus: http.StatusBadGateway, Status: cmstypes.StatusRec{Code: http.StatusInternalServerError, Message: "500 Internal server error"}, } client := app.creditCallClient() trResult, err := start(client) if err != nil { logging.Error(types.ServiceName, err.Error(), "Start transaction error", string(op), "", "", 0) outcome.FailureType = types.ResultError outcome.FailureDescription = "No response from payment processor" return outcome } var result creditcall.PaymentResult result.FillFromTransactionResult(trResult) res := result.Fields[types.TransactionResult] if !strings.EqualFold(res, types.ResultApproved) { app.printCreditCallReceipt(result.CardholderReceipt) desc := result.Fields[types.ErrorDescription] if desc == "" { desc = result.Fields[types.Errors] } logging.Error(types.ServiceName, "Preauthorization failed", "Result: "+res+" Description: "+desc, string(op), "", app.cfg.Hotel, app.cfg.Kiosk) outcome.HTTPStatus = http.StatusOK outcome.Status = result.Status outcome.Payment = result outcome.FailureType = res outcome.FailureDescription = result.Fields[types.Errors] return outcome } ref := result.Fields[types.Reference] log.Printf("Preauth approved, reference: %s. Sending confirm...", ref) body, err := confirmWithRetry(client, creditcall.ConfirmTransactionRequest{ Amount: request.AmountMinorUnits, Reference: ref, }, 2) if err != nil { logging.Error(types.ServiceName, err.Error(), "Confirm transaction error", string(op), "", "", 0) mail.SendEmailOnError(app.cfg.Hotel, app.cfg.Kiosk, "Payment confirmation failed", "Reference: "+ref+", Error: "+err.Error()) outcome.FailureType = types.ResultError outcome.FailureDescription = "ConfirmTransactionError" return outcome } // Intentionally reuse both records: XML appends confirmation entries, and // missing confirmation fields/receipts retain their existing legacy behavior. if err := trResult.ParseTransactionResult(body); err != nil { logging.Error(types.ServiceName, err.Error(), "Parse confirm result error", string(op), "", "", 0) } result.FillFromTransactionResult(trResult) res = result.Fields[types.TransactionResult] if !strings.EqualFold(res, types.ResultApproved) { app.printCreditCallReceipt(result.CardholderReceipt) desc := result.Fields[types.ErrorDescription] if desc == "" { desc = result.Fields[types.Errors] } logging.Error(types.ServiceName, "Transaction not approved after confirm", "Confirm result: "+res+" Description: "+desc, string(op), "", app.cfg.Hotel, app.cfg.Kiosk) mail.SendEmailOnError(app.cfg.Hotel, app.cfg.Kiosk, "Payment confirmation failed", "Reference: "+ref+", Confirm result: "+res+" Description: "+desc) outcome.HTTPStatus = http.StatusOK outcome.Status = result.Status outcome.Payment = result outcome.FailureType = res outcome.FailureDescription = result.Fields[types.Errors] return outcome } app.printCreditCallReceipt(result.CardholderReceipt) log.Printf("Transaction approved and confirmed, reference: %s", ref) outcome.HTTPStatus = http.StatusOK outcome.Status = result.Status outcome.Payment = result outcome.Approved = true return outcome } func (outcome creditCallSaleOutcome) legacyResponse() cmstypes.ResponseRec { response := cmstypes.ResponseRec{Status: outcome.Status} if outcome.Approved { response.Data = creditcall.BuildSuccessURL(outcome.Payment.Fields) } else { response.Data = creditcall.BuildFailureURL(outcome.FailureType, outcome.FailureDescription) } return response }