hardlink/payment/creditcall.go
2025-12-22 18:11:05 +00:00

184 lines
4.9 KiB
Go

package payment
import (
"encoding/hex"
"encoding/xml"
"fmt"
"net/http"
"net/url"
"strings"
"gitea.futuresens.co.uk/futuresens/cmstypes"
"gitea.futuresens.co.uk/futuresens/hardlink/types"
_ "github.com/denisenkom/go-mssqldb"
log "github.com/sirupsen/logrus"
)
// XML parsing structs
type (
TransactionRec struct {
XMLName xml.Name `xml:"TransactionPayload"`
AmountMinorUnits string `xml:"amount"`
TransactionType string `xml:"transactionType"`
}
TransactionResultXML struct {
XMLName xml.Name `xml:"TransactionResult"`
Entries []EntryXML `xml:"Entry"`
}
EntryXML struct {
Key string `xml:"Key"`
Value string `xml:"Value"`
}
TransactionConfirmation struct {
XMLName xml.Name `xml:"TransactionConfirmation"`
Result string `xml:"Result"`
Errors string `xml:"Errors"`
ErrorDescription string `xml:"ErrorDescription"`
ReceiptDataCardholder string `xml:"ReceiptDataCardholder"`
}
PaymentResult struct {
Fields map[string]string
CardholderReceipt string
Status cmstypes.StatusRec
}
TransactionInfo struct {
transactionRes string
transactionState string
}
)
// ParseTransactionResult parses the XML into entries.
func (tr *TransactionResultXML) ParseTransactionResult(data []byte) error {
if err := xml.Unmarshal(data, &tr); err != nil {
return fmt.Errorf("XML unmarshal: %w", err)
}
return nil
}
func (ti *TransactionInfo) FillFromTransactionResult(trResult TransactionResultXML) {
for _, e := range trResult.Entries {
switch e.Key {
case types.TransactionResult:
ti.transactionRes = e.Value
case types.TransactionState:
ti.transactionState = e.Value
}
}
}
func (r *PaymentResult) FillFromTransactionResult(trResult TransactionResultXML) {
if r.Fields == nil {
r.Fields = make(map[string]string)
}
for _, e := range trResult.Entries {
switch e.Key {
case types.ReceiptData, types.ReceiptDataMerchant:
// intentionally ignored
case types.ReceiptDataCardholder:
r.CardholderReceipt = e.Value
case types.TransactionResult:
r.Status.Message = e.Value
r.Status.Code = http.StatusOK
r.Fields[e.Key] = e.Value
default:
r.Fields[e.Key] = e.Value
}
}
}
// BuildRedirectURL builds the redirect URL to send the guest to after payment.
func BuildPaymentRedirectURL(result map[string]string) string {
res := result[types.TransactionResult]
// Transaction approved?
if strings.EqualFold(res, types.ResultApproved) {
// Transaction confirmed?
if strings.EqualFold(result[types.ConfirmResult], types.ResultApproved) {
log.WithField(types.LogResult, result[types.ConfirmResult]).
Info("Transaction approved and confirmed")
return buildSuccessURL(result)
}
// Not confirmed
log.WithFields(log.Fields{types.LogFieldError: result[types.ConfirmResult], types.LogFieldDescription: result[types.ConfirmErrors]}).
Error("Transaction approved but not confirmed")
return BuildFailureURL(result[types.ConfirmResult], result[types.ConfirmErrors])
}
// Not approved
return BuildFailureURL(res, result[types.Errors])
}
func BuildPreauthRedirectURL(result map[string]string) (string, bool) {
res := result[types.TransactionResult]
tType := result[types.TransactionType]
// Transaction approved?
if strings.EqualFold(res, types.ResultApproved) {
switch {
// Transaction type AccountVerification?
case strings.EqualFold(tType, types.AccountVerificationType):
log.WithField(types.LogResult, result[types.TransactionResult]).
Info("Account verification approved")
return buildSuccessURL(result), false
// Transaction type Sale?
case strings.EqualFold(tType, types.SaleTransactionType):
// Transaction confirmed?
log.WithField(types.LogResult, result[types.ConfirmResult]).
Info("Amount preauthorized successfully")
return buildSuccessURL(result), true
}
}
// Not approved
return BuildFailureURL(res, result[types.Errors]), false
}
func buildSuccessURL(result map[string]string) string {
q := url.Values{}
q.Set("CardNumber", hex.EncodeToString([]byte(result[types.PAN_MASKED])))
q.Set("ExpiryDate", hex.EncodeToString([]byte(result[types.EXPIRY_DATE])))
q.Set("TxnReference", result[types.Reference])
q.Set("CardHash", hex.EncodeToString([]byte(result[types.CardHash])))
q.Set("CardReference", hex.EncodeToString([]byte(result[types.CardReference])))
return (&url.URL{
Path: types.CheckinSuccessfulEndpoint,
RawQuery: q.Encode(),
}).String()
}
func BuildFailureURL(msgType, description string) string {
q := url.Values{}
if msgType != "" {
description = fmt.Sprintf("Transaction %s", strings.ToLower(msgType))
}
if description != "" {
msgType = types.ResultError
}
log.WithFields(log.Fields{types.LogFieldError: msgType, types.LogFieldDescription: description}).
Error("Transaction failed")
q.Set("MsgType", msgType)
q.Set("Description", description)
return (&url.URL{
Path: types.CheckinUnsuccessfulEndpoint,
RawQuery: q.Encode(),
}).String()
}