184 lines
4.9 KiB
Go
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()
|
|
}
|