github.com/companieshouse/lfp-pay-api@v0.0.0-20230203133422-0ca455cd79f9/handlers/patch_payable_resource.go (about)

     1  package handlers
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"sync"
     8  
     9  	"github.com/companieshouse/chs.go/log"
    10  	"github.com/companieshouse/lfp-pay-api-core/models"
    11  	"github.com/companieshouse/lfp-pay-api-core/validators"
    12  	"github.com/companieshouse/lfp-pay-api/config"
    13  	"github.com/companieshouse/lfp-pay-api/e5"
    14  	"github.com/companieshouse/lfp-pay-api/service"
    15  	"github.com/companieshouse/lfp-pay-api/utils"
    16  	"gopkg.in/go-playground/validator.v9"
    17  )
    18  
    19  // handleEmailKafkaMessage allows us to mock the call to sendEmailKafkaMessage for unit tests
    20  var handleEmailKafkaMessage = service.SendEmailKafkaMessage
    21  
    22  var wg sync.WaitGroup
    23  
    24  // PayResourceHandler will update the resource to mark it as paid and also tell the finance system that the
    25  // transaction(s) associated with it are paid.
    26  func PayResourceHandler(svc *service.PayableResourceService, e5Client *e5.Client) http.Handler {
    27  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    28  		// 1. get the payable resource our of the context. authorisation is already handled in the interceptor
    29  		i := r.Context().Value(config.PayableResource)
    30  		if i == nil {
    31  			err := fmt.Errorf("no payable resource in context. check PayableAuthenticationInterceptor is installed")
    32  			log.ErrorR(r, err)
    33  			m := models.NewMessageResponse("no payable request present in request context")
    34  			utils.WriteJSONWithStatus(w, r, m, http.StatusBadRequest)
    35  			return
    36  		}
    37  
    38  		resource := i.(*models.PayableResource)
    39  
    40  		log.Info("processing LFP payment", log.Data{
    41  			"lfp_reference":  resource.Reference,
    42  			"company_number": resource.CompanyNumber,
    43  		})
    44  
    45  		// 2. validate the request and check the reference number against the payment api to validate that is has
    46  		// actually been paid
    47  		var request models.PatchResourceRequest
    48  		err := json.NewDecoder(r.Body).Decode(&request)
    49  		if err != nil {
    50  			log.ErrorR(r, err, log.Data{"lfp_reference": resource.Reference})
    51  			m := models.NewMessageResponse("there was a problem reading the request body")
    52  			utils.WriteJSONWithStatus(w, r, m, http.StatusBadRequest)
    53  			return
    54  		}
    55  		v := validator.New()
    56  		err = v.Struct(request)
    57  
    58  		if err != nil {
    59  			log.ErrorR(r, err, log.Data{"lfp_reference": resource.Reference, "payment_id": request.Reference})
    60  			m := models.NewMessageResponse("the request contained insufficient data and/or failed validation")
    61  			utils.WriteJSONWithStatus(w, r, m, http.StatusBadRequest)
    62  			return
    63  		}
    64  
    65  		payment, err := service.GetPaymentInformation(request.Reference, r)
    66  		if err != nil {
    67  			log.ErrorR(r, err, log.Data{"lfp_reference": resource.Reference, "payment_id": request.Reference})
    68  			m := models.NewMessageResponse("the payable resource does not exist")
    69  			utils.WriteJSONWithStatus(w, r, m, http.StatusBadRequest)
    70  			return
    71  		}
    72  
    73  		err = validators.New().ValidateForPayment(*resource, *payment)
    74  		if err != nil {
    75  			m := models.NewMessageResponse("there was a problem validating this payment")
    76  			utils.WriteJSONWithStatus(w, r, m, http.StatusBadRequest)
    77  			return
    78  		}
    79  
    80  		wg.Add(3)
    81  
    82  		go sendConfirmationEmail(resource, payment, r, w)
    83  		go updateDatabase(resource, payment, svc, r, w)
    84  		go updateE5(e5Client, resource, payment, svc, r, w)
    85  
    86  		wg.Wait()
    87  
    88  		w.Header().Set("Content-Type", "application/json")
    89  		w.WriteHeader(http.StatusNoContent) // This will not be set if status has already been set
    90  	})
    91  }
    92  func sendConfirmationEmail(resource *models.PayableResource, payment *validators.PaymentInformation, r *http.Request, w http.ResponseWriter) {
    93  	// Send confirmation email
    94  	defer wg.Done()
    95  	err := handleEmailKafkaMessage(*resource, r)
    96  	if err != nil {
    97  		log.ErrorR(r, err, log.Data{"lfp_reference": resource.Reference, "payment_id": payment.Reference})
    98  		w.WriteHeader(http.StatusInternalServerError)
    99  		return
   100  	}
   101  
   102  	log.Info("confirmation email sent to customer", log.Data{
   103  		"lfp_reference":  resource.Reference,
   104  		"company_number": resource.CompanyNumber,
   105  		"email_address":  resource.CreatedBy.Email,
   106  	})
   107  }
   108  
   109  func updateDatabase(resource *models.PayableResource, payment *validators.PaymentInformation, svc *service.PayableResourceService, r *http.Request, w http.ResponseWriter) {
   110  	// Update the payable resource in the db
   111  	defer wg.Done()
   112  	err := svc.UpdateAsPaid(*resource, *payment)
   113  	if err != nil {
   114  		log.ErrorR(r, err, log.Data{"lfp_reference": resource.Reference, "payment_id": payment.Reference})
   115  		w.WriteHeader(http.StatusInternalServerError)
   116  		return
   117  	}
   118  
   119  	log.Info("payment resource is now marked as paid in db", log.Data{
   120  		"lfp_reference":  resource.Reference,
   121  		"company_number": resource.CompanyNumber,
   122  	})
   123  }
   124  
   125  func updateE5(e5Client *e5.Client, resource *models.PayableResource, payment *validators.PaymentInformation, svc *service.PayableResourceService, r *http.Request, w http.ResponseWriter) {
   126  	// Mark the resource as paid in e5
   127  	defer wg.Done()
   128  	err := service.MarkTransactionsAsPaid(svc, e5Client, *resource, *payment)
   129  	if err != nil {
   130  		log.ErrorR(r, err, log.Data{
   131  			"lfp_reference":  resource.Reference,
   132  			"company_number": resource.CompanyNumber,
   133  		})
   134  		w.WriteHeader(http.StatusInternalServerError)
   135  		return
   136  	}
   137  }