github.com/mundipagg/boleto-api@v0.0.0-20230620145841-3f9ec742599f/bank/services/jpmorgan/jpmorgan.go (about)

     1  package jpmorgan
     2  
     3  import (
     4  	"encoding/json"
     5  	"encoding/xml"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/PMoneda/flow"
    12  	"github.com/mundipagg/boleto-api/certificate"
    13  	"github.com/mundipagg/boleto-api/config"
    14  	"github.com/mundipagg/boleto-api/infrastructure/token"
    15  	"github.com/mundipagg/boleto-api/log"
    16  	"github.com/mundipagg/boleto-api/metrics"
    17  	"github.com/mundipagg/boleto-api/models"
    18  	"github.com/mundipagg/boleto-api/tmpl"
    19  	"github.com/mundipagg/boleto-api/util"
    20  	"github.com/mundipagg/boleto-api/validations"
    21  )
    22  
    23  var (
    24  	onceTransport = &sync.Once{}
    25  	transportTLS  *http.Transport
    26  )
    27  
    28  type bankJPMorgan struct {
    29  	validate  *models.Validator
    30  	log       *log.Log
    31  	transport *http.Transport
    32  	jwtSigner token.JwtGenerator
    33  }
    34  
    35  func New() (bankJPMorgan, error) {
    36  	var err error
    37  	b := bankJPMorgan{
    38  		validate: models.NewValidator(),
    39  		log:      log.CreateLog(),
    40  	}
    41  
    42  	certificates := certificate.TLSCertificate{
    43  		Crt: config.Get().AzureStorageJPMorganCrtName,
    44  		Key: config.Get().AzureStorageJPMorganPkName,
    45  	}
    46  
    47  	onceTransport.Do(func() {
    48  		transportTLS, err = util.BuildTLSTransport(certificates)
    49  	})
    50  	b.transport = transportTLS
    51  
    52  	if err != nil || (b.transport == nil && !config.Get().MockMode) {
    53  		return bankJPMorgan{}, fmt.Errorf("fail on load TLSTransport: %v", err)
    54  	}
    55  
    56  	b.validate.Push(validations.ValidateAmount)
    57  	b.validate.Push(validations.ValidateExpireDate)
    58  	b.validate.Push(validations.ValidateBuyerDocumentNumber)
    59  	b.validate.Push(validations.ValidateRecipientDocumentNumber)
    60  
    61  	b.jwtSigner = token.GetJwtGenerator("RS256")
    62  
    63  	return b, nil
    64  }
    65  
    66  func (b bankJPMorgan) ProcessBoleto(boleto *models.BoletoRequest) (models.BoletoResponse, error) {
    67  	errs := b.ValidateBoleto(boleto)
    68  	if len(errs) > 0 {
    69  		return models.BoletoResponse{Errors: errs}, nil
    70  	}
    71  
    72  	return b.RegisterBoleto(boleto)
    73  }
    74  
    75  func (b bankJPMorgan) ValidateBoleto(request *models.BoletoRequest) models.Errors {
    76  	return models.Errors(b.validate.Assert(request))
    77  }
    78  
    79  func (b bankJPMorgan) GetBankNumber() models.BankNumber {
    80  	return models.JPMorgan
    81  }
    82  
    83  func (b bankJPMorgan) GetBankNameIntegration() string {
    84  	return "JPMorgan"
    85  }
    86  
    87  func (b bankJPMorgan) GetErrorsMap() map[string]int {
    88  	var erros = map[string]int{
    89  		"BOL-5":                          http.StatusBadRequest,
    90  		"BOL-7":                          http.StatusBadRequest,
    91  		"BOL-144":                        http.StatusBadRequest,
    92  		"GCA-001":                        http.StatusBadGateway,
    93  		"GCA-111":                        http.StatusBadRequest,
    94  		"GCA-003":                        http.StatusBadGateway,
    95  		"BOL-2":                          http.StatusBadRequest,
    96  		"BOL-3":                          http.StatusBadRequest,
    97  		"BOL-4":                          http.StatusBadRequest,
    98  		"BOL-1":                          http.StatusBadGateway,
    99  		"Signature Verification Failure": http.StatusInternalServerError,
   100  		"Authentication Failure":         http.StatusInternalServerError,
   101  		"Internal Error - Contact Service Provider": http.StatusBadGateway,
   102  	}
   103  	return erros
   104  }
   105  
   106  func (b bankJPMorgan) Log() *log.Log {
   107  	return b.log
   108  }
   109  
   110  func (b bankJPMorgan) RegisterBoleto(boleto *models.BoletoRequest) (models.BoletoResponse, error) {
   111  	var response string
   112  	var respHeader map[string]interface{}
   113  	var status int
   114  	var err error
   115  
   116  	JPMorganURL := config.Get().URLJPMorgan
   117  	boleto.Title.BoletoType, boleto.Title.BoletoTypeCode = getBoletoType(boleto)
   118  
   119  	body := flow.NewFlow().From("message://?source=inline", boleto, templateRequest, tmpl.GetFuncMaps()).GetBody().(string)
   120  	head := hearders()
   121  
   122  	bodyEncripted, encryptedErr := b.encriptedBody(body)
   123  	if encryptedErr != nil {
   124  		return *encryptedErr, nil
   125  	}
   126  
   127  	b.log.Request(body, JPMorganURL, getLogRequestProperties(head, bodyEncripted))
   128  
   129  	duration := util.Duration(func() {
   130  		if config.Get().MockMode {
   131  			response, respHeader, status, err = util.PostWithHeader(JPMorganURL, body, config.Get().TimeoutDefault, head)
   132  		} else {
   133  			response, respHeader, status, err = util.PostTLSWithHeader(JPMorganURL, bodyEncripted, config.Get().TimeoutDefault, head, b.transport)
   134  		}
   135  
   136  	})
   137  	metrics.PushTimingMetric("jpmorgan-register-boleto-time", duration.Seconds())
   138  
   139  	b.log.Response(response, JPMorganURL, nil)
   140  
   141  	return mapJPMorganResponse(boleto, getContentType(respHeader), response, status, err), nil
   142  }
   143  
   144  func (b bankJPMorgan) encriptedBody(body string) (string, *models.BoletoResponse) {
   145  	errorResponse := models.GetBoletoResponseError("MP500", "Encript error")
   146  	sk, err := certificate.GetCertificateFromStore(config.Get().AzureStorageJPMorganSignCrtName)
   147  	if err != nil {
   148  		return "", &errorResponse
   149  	}
   150  	bodyEncripted, err := b.jwtSigner.Sign(body, sk.([]byte))
   151  	if err != nil {
   152  		return "", &errorResponse
   153  	}
   154  	return bodyEncripted, nil
   155  }
   156  
   157  func getContentType(respHeader map[string]interface{}) string {
   158  	return respHeader["Content-Type"].(string)
   159  }
   160  
   161  func getLogRequestProperties(header map[string]string, encryptedBody string) map[string]string {
   162  	m := make(map[string]string)
   163  	m["encryptedBody"] = encryptedBody
   164  	for k, v := range header {
   165  		m[k] = v
   166  	}
   167  	return m
   168  }
   169  
   170  func hearders() map[string]string {
   171  	return map[string]string{"Content-Type": "text/xml"}
   172  }
   173  
   174  func getBoletoType(boleto *models.BoletoRequest) (bt string, btc string) {
   175  	return "DM", "02"
   176  }
   177  
   178  func mapJPMorganResponse(request *models.BoletoRequest, contentType string, response string, status int, httpErr error) models.BoletoResponse {
   179  	f := flow.NewFlow().To("set://?prop=body", response)
   180  	switch status {
   181  	case 200:
   182  		f.To("transform://?format=json", templateResponse, templateAPI, tmpl.GetFuncMaps())
   183  		f.To("unmarshall://?format=json", new(models.BoletoResponse))
   184  	case 0, 504:
   185  		return models.GetBoletoResponseError("MPTimeout", timeoutMessage(httpErr))
   186  	case 401, 500:
   187  		if contentType == "application/xml" {
   188  			f.To("set://?prop=body", parseXmlErrorToJson(response))
   189  			f.To("transform://?format=json", templateErrorXmltoJson, templateAPI, tmpl.GetFuncMaps())
   190  		} else if contentType == "application/json" {
   191  			f.To("transform://?format=json", templateErrorJson, templateAPI, tmpl.GetFuncMaps())
   192  		}
   193  
   194  		f.To("unmarshall://?format=json", new(models.BoletoResponse))
   195  	case 400, 403, 404:
   196  		dataError := util.ParseJSON(response, new(arrayDataError)).(*arrayDataError)
   197  		f.To("set://?prop=body", strings.Replace(util.Stringify(dataError.Error[0]), "\\\"", "", -1))
   198  		f.To("transform://?format=json", templateErrorJson, templateAPI, tmpl.GetFuncMaps())
   199  		f.To("unmarshall://?format=json", new(models.BoletoResponse))
   200  	case 409, 422:
   201  		f.To("transform://?format=json", templateErrorBoletoJson, templateAPI, tmpl.GetFuncMaps())
   202  		f.To("unmarshall://?format=json", new(models.BoletoResponse))
   203  	}
   204  
   205  	switch t := f.GetBody().(type) {
   206  	case *models.BoletoResponse:
   207  		if hasOurNumberFail(t) {
   208  			return models.GetBoletoResponseError("MPOurNumberFail", "our number was not returned by the bank")
   209  		} else {
   210  			return *t
   211  		}
   212  	}
   213  	return models.GetBoletoResponseError("MP500", "Internal Error")
   214  }
   215  
   216  func parseXmlErrorToJson(xmlBody string) string {
   217  	data := &ServiceMessageError{}
   218  	err := xml.Unmarshal([]byte(xmlBody), data)
   219  	if err != nil {
   220  		panic(err)
   221  	}
   222  	json, err := json.Marshal(data)
   223  	if err != nil {
   224  		panic(err)
   225  	}
   226  	return string(json)
   227  }
   228  
   229  func timeoutMessage(err error) string {
   230  	var msg string
   231  	if err != nil {
   232  		msg = fmt.Sprintf("%v", err)
   233  	} else {
   234  		msg = "GatewayTimeout"
   235  	}
   236  	return msg
   237  }
   238  
   239  func hasOurNumberFail(response *models.BoletoResponse) bool {
   240  	return !response.HasErrors() && (response.OurNumber == "" || response.OurNumber == "000000000000")
   241  }