131 lines
4.9 KiB
Go
131 lines
4.9 KiB
Go
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
|
|
}
|