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() }