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 }