diff --git a/handlers/handlers.go b/handlers/handlers.go index 63c8f6e..79ae2b1 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -43,6 +43,7 @@ func (app *App) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("/issuedoorcard", app.issueDoorCard) mux.HandleFunc("/printroomticket", app.printRoomTicket) mux.HandleFunc("/starttransaction", app.startTransaction) + mux.HandleFunc("/startandconfirmtransaction", app.startAndConfirmTransaction) } func (app *App) startTransaction(w http.ResponseWriter, r *http.Request) { @@ -95,7 +96,106 @@ func (app *App) startTransaction(w http.ResponseWriter, r *http.Request) { writeTransactionResult(w, http.StatusBadRequest, theResponse) return } - log.Printf("Start trnasaction payload: Amount=%s, Type=%s", theRequest.AmountMinorUnits, theRequest.TransactionType) + log.Printf("Transaction payload: Amount=%s, Type=%s", theRequest.AmountMinorUnits, theRequest.TransactionType) + + client := &http.Client{Timeout: 300 * time.Second} + response, err := client.Post(transactionUrl, "text/xml", bytes.NewBuffer(body)) + if err != nil { + logging.Error(serviceName, err.Error(), "Payment processing error", string(op), "", "", 0) + theResponse.Data = payment.BuildFailureURL(payment.ResultError, "No response from payment processor") + writeTransactionResult(w, http.StatusBadGateway, theResponse) + return + } + defer response.Body.Close() + + body, err = io.ReadAll(response.Body) + if err != nil { + logging.Error(serviceName, err.Error(), "Read response body error", string(op), "", "", 0) + theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Failed to read response body") + writeTransactionResult(w, http.StatusInternalServerError, theResponse) + return + } + + if err := trResult.ParseTransactionResult(body); err != nil { + logging.Error(serviceName, err.Error(), "Parse transaction result error", string(op), "", "", 0) + } + + // Compose JSON from responseEntries + result := make(map[string]string) + for _, e := range trResult.Entries { + switch e.Key { + case payment.ReceiptData, payment.ReceiptDataMerchant: + // ignore these + case payment.ReceiptDataCardholder: + cardholderReceipt = e.Value + case payment.TransactionResult: + theResponse.Status.Message = e.Value + theResponse.Status.Code = http.StatusOK + result[e.Key] = e.Value + default: + result[e.Key] = e.Value + } + } + + if err := printer.PrintCardholderReceipt(cardholderReceipt); err != nil { + log.Errorf("PrintCardholderReceipt error: %v", err) + } + + theResponse.Data = payment.BuildPaymentRedirectURL(result) + writeTransactionResult(w, http.StatusOK, theResponse) +} + +func (app *App) startAndConfirmTransaction(w http.ResponseWriter, r *http.Request) { + const op = logging.Op("startAndConfirmTransaction") + var ( + theResponse cmstypes.ResponseRec + cardholderReceipt string + theRequest cmstypes.TransactionRec + trResult payment.TransactionResultXML + ) + + theResponse.Status.Code = http.StatusInternalServerError + theResponse.Status.Message = "500 Internal server error" + + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + w.Header().Set("Content-Type", "application/json") + + if !app.isPayment { + theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Payment processing is disabled") + writeTransactionResult(w, http.StatusServiceUnavailable, theResponse) + return + } + + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusNoContent) + return + } + + log.Println("startAndConfirmTransaction called") + if r.Method != http.MethodPost { + theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Method not allowed; use POST") + writeTransactionResult(w, http.StatusMethodNotAllowed, theResponse) + return + } + defer r.Body.Close() + + if ct := r.Header.Get("Content-Type"); ct != "text/xml" { + theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Content-Type must be text/xml") + writeTransactionResult(w, http.StatusUnsupportedMediaType, theResponse) + return + } + + body, _ := io.ReadAll(r.Body) + err := xml.Unmarshal(body, &theRequest) + if err != nil { + logging.Error(serviceName, err.Error(), "ReadXML", string(op), "", "", 0) + theResponse.Data = payment.BuildFailureURL(payment.ResultError, "Invalid XML payload") + writeTransactionResult(w, http.StatusBadRequest, theResponse) + return + } + log.Printf("Transaction payload: Amount=%s, Type=%s", theRequest.AmountMinorUnits, theRequest.TransactionType) client := &http.Client{Timeout: 300 * time.Second} response, err := client.Post(transactionUrl, "text/xml", bytes.NewBuffer(body)) diff --git a/payment/creditcall.go b/payment/creditcall.go index faf53c0..a688ab6 100644 --- a/payment/creditcall.go +++ b/payment/creditcall.go @@ -248,7 +248,7 @@ func nullableFloatArg(nf sql.NullFloat64) interface{} { } // BuildRedirectURL builds the redirect URL to send the guest to after payment. -func BuildRedirectURL(result map[string]string) string { +func BuildPaymentRedirectURL(result map[string]string) string { res := result[TransactionResult] tType := result[TransactionType] @@ -284,6 +284,34 @@ func BuildRedirectURL(result map[string]string) string { return BuildFailureURL(res, result[Errors]) } +func BuildPreauthRedirectURL(result map[string]string) string { + res := result[TransactionResult] + tType := result[TransactionType] + + // Transaction approved? + if strings.EqualFold(res, ResultApproved) { + switch { + // Transaction type AccountVerification? + case strings.EqualFold(tType, AccountVerificationType): + log.WithField(LogResult, result[TransactionResult]). + Info("Account verification approved") + + return buildSuccessURL(result) + + // Transaction type Sale? + case strings.EqualFold(tType, SaleTransactionType): + // Transaction confirmed? + log.WithField(LogResult, result[ConfirmResult]). + Info("Transaction approved and confirmed") + + return buildSuccessURL(result) + } + } + + // Not approved + return BuildFailureURL(res, result[Errors]) +} + func buildSuccessURL(result map[string]string) string { q := url.Values{} q.Set("TxnReference", result[Reference])