diff --git a/git b/git new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/billpaymentservice/bbps/config/SecurityConfig.java b/src/main/java/com/billpaymentservice/bbps/config/SecurityConfig.java index a35a36d..eb2b7db 100644 --- a/src/main/java/com/billpaymentservice/bbps/config/SecurityConfig.java +++ b/src/main/java/com/billpaymentservice/bbps/config/SecurityConfig.java @@ -2,6 +2,7 @@ package com.billpaymentservice.bbps.config; import java.util.Arrays; +import com.billpaymentservice.bbps.controller.AgentController; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -18,13 +19,16 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @EnableWebSecurity public class SecurityConfig { + private final AgentController agentController; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final JwtFilter jwtFilter; - public SecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtFilter jwtFilter) { + public SecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtFilter jwtFilter, AgentController agentController) { this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint; this.jwtFilter = jwtFilter; + this.agentController = agentController; } @Bean diff --git a/src/main/java/com/billpaymentservice/bbps/controller/PaymentPollBackController.java b/src/main/java/com/billpaymentservice/bbps/controller/PaymentPollBackController.java index bfc1828..56f9898 100644 --- a/src/main/java/com/billpaymentservice/bbps/controller/PaymentPollBackController.java +++ b/src/main/java/com/billpaymentservice/bbps/controller/PaymentPollBackController.java @@ -6,17 +6,17 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import com.billpaymentservice.bbps.dto.ResponseModel; -import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest; import com.billpaymentservice.bbps.dto.request.PollBackRequest; import com.billpaymentservice.bbps.service.PollBackService; @RequestMapping("/payment") +@RestController public class PaymentPollBackController { - - private final PollBackService pollBackService; + private final PollBackService pollBackService; public PaymentPollBackController(PollBackService pollBackService) { this.pollBackService = pollBackService; diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java index a283957..caf5f60 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java @@ -47,10 +47,14 @@ import com.billpaymentservice.bbps.entites.PaymentStartRecord; public class PaymentStartServiceImpl implements PaymentStartService { private static final Logger LOGGER = LoggerFactory.getLogger(PaymentStartServiceImpl.class); - - private String uatPaymentUrlweb="https://uat.godemo.in/api/UATIntegration/uat/payin/paymentrequest"; - private String uatPaymentUrlintent="https://uat.godemo.in/api/UATIntegration/paymentrequest/seamless"; - + @Value("${payment.enckey}") + private String paymentEncryKey; + @Value("${payment.mid}") + private String paymentMid; + @Value("${payment.callbackurl}") + private String paymentcallbackUrl; + @Value("${payment.companyName}") + private String paymentcompanyName; private final PaymentStartRecordRepository paymentStartRecordRepository; private final HttpService httpService; private final EncryptionUtil encryptionUtil; @@ -79,173 +83,86 @@ public class PaymentStartServiceImpl implements PaymentStartService { public ResponseModel startIntentPayment(PaymentStartUserRequest request) { String requestId = request.getReqId(); - String apiName = "StartPayment"; + String apiName = BBPSConstant.API_START_PAYMENT; String txnId = helper.generateTxnId(15); + LOGGER.info("MID IS:{}",paymentMid); + LOGGER.info("[startIntentPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); helper.validateMerchant(request.getMid()); // inbound log String requestJson = helper.convertObjectToJson(request); - InboundTraceLog inboundLog = - helper.createInboundLog(requestId, txnId, apiName, request.getMid(), - BBPSConstant.HTTP_POST, requestJson); + InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, request.getMid(), BBPSConstant.HTTP_POST, requestJson); + LOGGER.debug("[startIntentPayment] inbound log created txnId={} requestId={}", txnId, requestId); - // Prepare request payload + // prepare and encrypt payload PaymentDetailSentRequest sent = createPaymentSentRequest(request); - String orderNo = "ORD" + helper.generateTxnId(12); + String orderNo = BBPSConstant.ORDER_PREFIX + helper.generateTxnId(12); sent.setOrderNo(orderNo); - + sent.setEnckey(paymentEncryKey); String plaintext = helper.convertObjectToJson(sent); - String encKey = BBPSConstant.enckey; - sent.setEnckey(encKey); - - String encrypted = encryptionUtil.encryptMData(plaintext, encKey); + String encrypted = encryptionUtil.encryptMData(plaintext, paymentEncryKey); PaymentRequestPayload finalPayload = new PaymentRequestPayload(); - finalPayload.setMid(BBPSConstant.mid); + finalPayload.setMid(paymentMid); finalPayload.setPayload(encrypted); - - String forwardJson = helper.convertObjectToJson(finalPayload); - LOGGER.info("Payment Start Request Payload: " + forwardJson); - OutboundTraceLog outboundLog = - helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrlintent, - BBPSConstant.HTTP_POST, forwardJson); + LOGGER.info("FINAL PAYLODA :{}",forwardJson); - // --- CALL EXTERNAL API --- - Map responseMap = - httpService.postRequest(uatPaymentUrlintent, forwardJson, null); - - LOGGER.info("Payment Start Response: " + responseMap); + LOGGER.info("[startIntentPayment] outbound payload prepared txnId={} orderNo={}", txnId, orderNo); + OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_INTENT, BBPSConstant.HTTP_POST, forwardJson); - int httpCode = Integer.parseInt(responseMap.getOrDefault("respCode", "500")); + // call external API + Map responseMap = httpService.postRequest(BBPSConstant.UAT_PAYMENT_URL_INTENT, forwardJson, null); + LOGGER.debug("[startIntentPayment] raw response map: {}", responseMap); + + int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); String respBody = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); helper.updateOutboundLogResponse(outboundLog, respBody); - // --- HTTP FAILURE --- - if (httpCode != 200) { + if (httpCode != BBPSConstant.HTTP_OK_INT) { helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); - - throw new CustomException( - "Upstream HTTP error", - "FAILED", - httpCode, - respBody - ); + LOGGER.error("[startIntentPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); + throw new CustomException(BBPSConstant.MSG_UPSTREAM_HTTP_ERROR, BBPSConstant.FAILED_STR, httpCode, respBody); } if (respBody == null || respBody.isEmpty()) { - helper.updateInboundLogFailure(inboundLog, "Empty response from payment provider", "500"); - - throw new CustomException( - "Empty response from payment provider", - "FAILED", - 500, - null - ); + helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); + LOGGER.error("[startIntentPayment] empty response txnId={}", txnId); + throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); } - // --- Convert JSON to object --- - PaymentStartApiResponse apiResponse = null; + // assume response has fixed format: { statusCode, message, responseData: { amount, orderNo, createdOn, qrString, mid, txnId } } + PaymentStartApiResponse apiResponse; try { - Map top = objectMapper.readValue(respBody, new TypeReference>(){}); - - // Pick inner payload if present - Map payload = top; - if (top.containsKey("data") && top.get("data") instanceof Map) { - //noinspection unchecked - payload = (Map) top.get("data"); - } - - // Simple failure detection: status == Failure OR statusCode != 200 - String status = String.valueOf(payload.getOrDefault("status", top.getOrDefault("status", ""))).trim(); - int outcomeCode = 0; - Object scObj = payload.getOrDefault("statusCode", top.get("statusCode")); - // prefer numeric status field if present - if (status.matches("\\d+")) { - try { outcomeCode = Integer.parseInt(status); } catch (NumberFormatException ignore) {} - } - // if not present, fall back to statusCode - if (outcomeCode == 0 && scObj != null) { - try { outcomeCode = Integer.parseInt(String.valueOf(scObj)); } catch (NumberFormatException ignore) {} - } - // default to 200 when nothing provided - if (outcomeCode == 0) outcomeCode = 200; - - String message = String.valueOf(payload.getOrDefault("message", top.getOrDefault("message", ""))); - - - // Handle special cases - if (outcomeCode == 200 || outcomeCode == 205 || outcomeCode == 206) { - // success - build apiResponse from wrapper or direct payload - if (top.containsKey("message") && (top.containsKey("responseData") || top.containsKey("data"))) { - apiResponse = objectMapper.convertValue(top, PaymentStartApiResponse.class); - } else { - Map dataMap = payload; - PaymentStartApiResponse.ResponseData rd = new PaymentStartApiResponse.ResponseData(); - rd.setAmount(String.valueOf(dataMap.getOrDefault("amount", ""))); - rd.setOrderNo(String.valueOf(dataMap.getOrDefault("orderNo", dataMap.getOrDefault("order_no", "")))); - rd.setCreatedOn(String.valueOf(dataMap.getOrDefault("created_on", dataMap.getOrDefault("createdOn", "")))); - rd.setQrString(String.valueOf(dataMap.getOrDefault("qrString", dataMap.getOrDefault("qr_string", "")))); - rd.setMid(String.valueOf(dataMap.getOrDefault("mid", ""))); - rd.setTxnId(String.valueOf(dataMap.getOrDefault("txnId", dataMap.getOrDefault("txn_id", "")))); - - apiResponse = new PaymentStartApiResponse(); - apiResponse.setMessage(String.valueOf(top.getOrDefault("message", "SUCCESS"))); - apiResponse.setStatusCode(String.valueOf(outcomeCode)); - apiResponse.setResponseData(rd); - } - - helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(outcomeCode)); - // proceed normally - } else if (outcomeCode == 198) { - // transaction processing - treat as non-final success/pending - helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(outcomeCode)); - return ResponseModel.builder() - .status(BBPSConstant.SENT_TO_PROCESSOR_STATUS) - .statusCode(outcomeCode) - .message(message == null || message.isEmpty() ? "Transaction processing" : message) - .data(null) - .build(); - } else if (outcomeCode == 1003) { - // warning - return a WARNING response; don't throw immediately - helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(outcomeCode)); - return ResponseModel.builder() - .status("WARNING") - .statusCode(outcomeCode) - .message(message == null || message.isEmpty() ? "Warning from merchant" : message) - .data(null) - .build(); - } else if (outcomeCode == 207) { - // Fraud rejected - explicit failure, handle exception - helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(outcomeCode)); - throw new CustomException(message == null || message.isEmpty() ? "Fraud Rejected" : message, - "FAILED", outcomeCode, respBody); - } else { - // Other failure codes (417,99,197,194,193 etc.) - helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(outcomeCode)); - throw new CustomException(message == null || message.isEmpty() ? "Upstream failure" : message, - "FAILED", outcomeCode, respBody); - } + apiResponse = objectMapper.readValue(respBody, PaymentStartApiResponse.class); } catch (JsonProcessingException e) { - LOGGER.error("Error parsing payment start API response JSON: {}", e.getMessage(), e); + LOGGER.error("[startIntentPayment] invalid JSON for expected response shape txnId={} err={}", txnId, e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, respBody, BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from payment API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_API, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, respBody); } - // Save record - // Determine response type: QR if qrString present, else JSON - String responseType = "JSON"; - String rawResponse = respBody == null ? "" : respBody; - if (apiResponse != null && apiResponse.getResponseData() != null - && apiResponse.getResponseData().getQrString() != null - && !apiResponse.getResponseData().getQrString().isEmpty()) { - responseType = "QR"; + // interpret numeric status + int outcomeCode = BBPSConstant.HTTP_OK_INT; + try { + outcomeCode = Integer.parseInt(String.valueOf(apiResponse.getStatusCode())); + } catch (Exception ignored) { } - bbpsMapperHelper.savePaymentStartRecord(request, apiResponse, txnId, orderNo, requestJson, rawResponse, responseType); - // Prepare DTO + if (!(outcomeCode == BBPSConstant.HTTP_OK_INT || outcomeCode == BBPSConstant.STATUS_CODE_205 || outcomeCode == BBPSConstant.STATUS_CODE_206)) { + helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(outcomeCode)); + LOGGER.error("[startIntentPayment] provider returned failure status txnId={} code={} message={}", txnId, outcomeCode, apiResponse.getMessage()); + throw new CustomException(apiResponse.getMessage() == null ? "Upstream failure" : apiResponse.getMessage(), BBPSConstant.FAILED_STR, outcomeCode, respBody); + } + + // success: persist and return + helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(outcomeCode)); + String responseType = (apiResponse.getResponseData() != null && apiResponse.getResponseData().getQrString() != null && !apiResponse.getResponseData().getQrString().isEmpty()) ? BBPSConstant.RESPONSE_TYPE_QR : BBPSConstant.RESPONSE_TYPE_JSON; + + bbpsMapperHelper.savePaymentStartRecord(request, apiResponse, txnId, orderNo, requestJson, respBody, responseType); + LOGGER.info("[startIntentPayment] saved record txnId={} orderNo={} responseType={}", txnId, orderNo, responseType); + PaymentStartReturnDto responseData = new PaymentStartReturnDto(); responseData.setAmount(apiResponse.getResponseData().getAmount()); responseData.setCreated_on(apiResponse.getResponseData().getCreatedOn()); @@ -253,12 +170,10 @@ public class PaymentStartServiceImpl implements PaymentStartService { responseData.setQrString(apiResponse.getResponseData().getQrString()); responseData.setTxnId(apiResponse.getResponseData().getTxnId()); - helper.updateInboundLogSuccess(inboundLog, respBody, - apiResponse.getStatusCode() == null ? "200" : apiResponse.getStatusCode().toString()); - + LOGGER.info("[startIntentPayment] complete txnId={} orderNo={} returning SUCCESS", txnId, orderNo); return ResponseModel.builder() - .status("SUCCESS") - .statusCode(httpCode) + .status(BBPSConstant.SUCCESS_RESPONSE) + .statusCode(BBPSConstant.HTTP_OK_INT) .message(apiResponse.getMessage()) .data(responseData) .build(); @@ -271,12 +186,12 @@ public class PaymentStartServiceImpl implements PaymentStartService { sent.setEmailId(request.getEmailId()); sent.setUserId(request.getMid()); sent.setCustomerVpa(request.getCustomerVpa()); - sent.setDateOfReg("2025-12-31"); - String effectiveMid = (request.getMid() != null && !request.getMid().isEmpty()) ? request.getMid() : BBPSConstant.mid; - sent.setMid(BBPSConstant.mid); + sent.setDateOfReg(BBPSConstant.DEFAULT_DATE_OF_REG); + // String effectiveMid = (request.getMid() != null && !request.getMid().isEmpty()) ? request.getMid() : paymentMid; + sent.setMid(paymentMid); sent.setMobileNo(request.getMobileNo()); - sent.setRespUrl(BBPSConstant.callbackurl); - sent.setTxnReqType("S"); + sent.setRespUrl(paymentcallbackUrl); + sent.setTxnReqType(BBPSConstant.TXN_REQ_TYPE); sent.setName(request.getName()); sent.setUdf1(request.getUdf1()); sent.setUdf2(request.getUdf2()); @@ -292,192 +207,211 @@ public class PaymentStartServiceImpl implements PaymentStartService { sent.setUdf12(request.getUdf12()); sent.setUdf13(request.getUdf13()); sent.setUdf14(request.getUdf14()); + LOGGER.debug("[createPaymentSentRequest] built sent request mid={} amount={}", sent.getMid(), sent.getAmount()); return sent; } @Override public ResponseModel startWebPayment(PaymentStartUserRequest request) { - String requestId = request.getReqId(); - String apiName = "StartPayment"; - String txnId = helper.generateTxnId(15); + String requestId = request.getReqId(); + String apiName = BBPSConstant.API_START_PAYMENT; + String txnId = helper.generateTxnId(15); - helper.validateMerchant(request.getMid()); + LOGGER.info("[startWebPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); - // inbound log - String requestJson = helper.convertObjectToJson(request); - InboundTraceLog inboundLog = - helper.createInboundLog(requestId, txnId, apiName, request.getMid(), - BBPSConstant.HTTP_POST, requestJson); + helper.validateMerchant(request.getMid()); - // Prepare request payload - PaymentDetailSentRequest sent = createPaymentSentRequest(request); - String orderNo = "ORD" + helper.generateTxnId(12); - sent.setOrderNo(orderNo); + // inbound log + String requestJson = helper.convertObjectToJson(request); + InboundTraceLog inboundLog = + helper.createInboundLog(requestId, txnId, apiName, request.getMid(), + BBPSConstant.HTTP_POST, requestJson); + LOGGER.debug("[startWebPayment] inbound log created txnId={} requestId={}", txnId, requestId); - String plaintext = helper.convertObjectToJson(sent); - String encKey = BBPSConstant.enckey; - sent.setEnckey(encKey); + // Prepare request payload + PaymentDetailSentRequest sent = createPaymentSentRequest(request); + String orderNo = "ORD" + helper.generateTxnId(12); + sent.setOrderNo(orderNo); - String encrypted = encryptionUtil.encryptMData(plaintext, encKey); + String plaintext = helper.convertObjectToJson(sent); + String encKey = paymentEncryKey; + sent.setEnckey(encKey); - // ----------- BUILD x-www-form-urlencoded PAYLOAD ----------- - String formPayload = - "mid=" + URLEncoder.encode(BBPSConstant.mid, StandardCharsets.UTF_8) + - "&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8); - LOGGER.info("Form Payload: {}", formPayload); - OutboundTraceLog outboundLog = - helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrlweb, - BBPSConstant.HTTP_POST, formPayload); + LOGGER.debug("[startWebPayment] plaintext prepared txnId={} orderNo={}", txnId, orderNo); - // ----------- CALL API (FORM URL-ENCODED) ----------- - Map responseMap = - httpService.postFormRequest(uatPaymentUrlweb, formPayload); + String encrypted = encryptionUtil.encryptMData(plaintext, encKey); + LOGGER.info("[startWebPayment] payload encrypted txnId={} orderNo={}", txnId, orderNo); - LOGGER.info("Payment Start Response: {}", responseMap); - - int httpCode = Integer.parseInt(responseMap.getOrDefault("respCode", "500")); - String respBody = responseMap.getOrDefault("data", ""); - /* - * Map parsedHtml = parseHtmlResponse(respBody); - * - * for(Map.Entry entry : parsedHtml.entrySet()) { - * LOGGER.info("Parsed HTML - {} : {}", entry.getKey(), entry.getValue()); } - */ - helper.updateOutboundLogResponse(outboundLog, respBody); + // ----------- BUILD x-www-form-urlencoded PAYLOAD ----------- + String formPayload = + "mid=" + URLEncoder.encode(paymentMid, StandardCharsets.UTF_8) + + "&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8); + LOGGER.info("[startWebPayment] Form Payload: {}", formPayload); + OutboundTraceLog outboundLog = + helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_WEB, + BBPSConstant.HTTP_POST, formPayload); + LOGGER.debug("[startWebPayment] outbound log created txnId={} url={}", txnId, BBPSConstant.UAT_PAYMENT_URL_WEB); - // ---- HTTP FAILURE ---- - if (httpCode != 200) { - helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); - throw new CustomException("Upstream HTTP Error", "FAILED", httpCode, respBody); - } + // ----------- CALL API (FORM URL-ENCODED) ----------- + Map responseMap = + httpService.postFormRequest(BBPSConstant.UAT_PAYMENT_URL_WEB, formPayload); - // ---- EMPTY RESPONSE ---- - if (respBody.isEmpty()) { - helper.updateInboundLogFailure(inboundLog, "Empty response from PG", "500"); - throw new CustomException("Empty response from PG", "FAILED", 500, null); - } + LOGGER.info("[startWebPayment] Payment Start Response: {}", responseMap); - // ❗❗ PG RETURNS HTML → NO JSON PARSING REQUIRED - helper.updateInboundLogSuccess(inboundLog, respBody, "200"); - - // Save record with HTML (persist original request and HTML response) - String responseTypeWeb = "HTML"; - String rawResponseWeb = respBody == null ? "" : respBody; - bbpsMapperHelper.savePaymentStartRecord(request, null, txnId, orderNo, requestJson, rawResponseWeb, responseTypeWeb); + // parse http code and response body using centralized keys + int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); + String respBody = responseMap.getOrDefault(BBPSConstant.RESPONSE_DATA_KEY, ""); + helper.updateOutboundLogResponse(outboundLog, respBody); + LOGGER.debug("[startWebPayment] updated outbound log response txnId={} httpCode={}", txnId, httpCode); - // Return HTML directly in API response - Map result = new HashMap<>(); - result.put("html", respBody); + // ---- HTTP FAILURE ---- + if (httpCode != BBPSConstant.HTTP_OK_INT) { + helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); + LOGGER.error("[startWebPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); + throw new CustomException("Upstream HTTP Error", BBPSConstant.FAILED_STR, httpCode, respBody); + } - return ResponseModel.builder() - .status("SUCCESS") - .statusCode(200) - .message("HTML Response Received") - .data(result) - .build(); - } + // ---- EMPTY RESPONSE ---- + if (respBody == null || respBody.isEmpty()) { + helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PG, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); + LOGGER.error("[startWebPayment] empty response from PG txnId={}", txnId); + throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PG, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); + } - @Override - public ResponseModel handleStartCallback(CallbackRequest callbackRequest) { - String apiName = "HANDLE_START_CALLBACK"; - LOGGER.info("[{}] START ", apiName); + // PG RETURNS HTML → NO JSON PARSING REQUIRED + helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(BBPSConstant.HTTP_OK_INT)); + LOGGER.info("[startWebPayment] received HTML response txnId={} length={}", txnId, respBody.length()); + + // Save record with HTML (persist original request and HTML response) + String responseTypeWeb = BBPSConstant.RESPONSE_TYPE_HTML; + String rawResponseWeb = respBody == null ? "" : respBody; + // bbpsMapperHelper.savePaymentStartRecord(request, null, txnId, orderNo, requestJson, rawResponseWeb, responseTypeWeb); + LOGGER.info("[startWebPayment] saved payment start record txnId={} orderNo={} responseType={}", txnId, orderNo, responseTypeWeb); - if (callbackRequest == null) { - LOGGER.warn("Callback payload is null"); - return new ResponseModel("Invalid callback", "205", 200, null); - } + // Return HTML directly in API response + Map result = new HashMap<>(); + result.put("html", respBody); - String mid = callbackRequest.getMid(); - String encryptedData = callbackRequest.getData(); - String signature = callbackRequest.getSignature(); + LOGGER.info("[startWebPayment] complete txnId={} orderNo={} returning HTML", txnId, orderNo); + return ResponseModel.builder() + .status(BBPSConstant.SUCCESS_RESPONSE) + .statusCode(BBPSConstant.HTTP_OK_INT) + .message(BBPSConstant.HTML_RESPONSE_MESSAGE) + .data(result) + .build(); + } - // Decrypt data - String decrypted = encryptionUtil.decryptMData(encryptedData, BBPSConstant.enckey); - if (decrypted == null) { - LOGGER.error("Failed to decrypt callback payload for mid={}", mid); - return new ResponseModel("Decryption failed", "207", 200, null); - } + @Override + public ResponseModel handleStartCallback(CallbackRequest callbackRequest) { + String apiName = "HANDLE_START_CALLBACK"; + LOGGER.info("[{}] START", apiName); - LOGGER.info("Decrypted data"); + if (callbackRequest == null) { + LOGGER.warn("[{}] Callback payload is null", apiName); + return new ResponseModel("Invalid callback", BBPSConstant.MERCHANT_ACK_NO_MATCH, BBPSConstant.HTTP_OK_INT, null); + } - // Parse decrypted JSON - Map payloadMap; - try { - payloadMap = objectMapper.readValue(decrypted, new TypeReference>(){}); - } catch (JsonProcessingException e) { - LOGGER.error("Failed to parse decrypted callback JSON: {}", e.getMessage(), e); - return new ResponseModel("Invalid decrypted JSON", "207", 200, null); - } + String mid = callbackRequest.getMid(); + String encryptedData = callbackRequest.getData(); + String signature = callbackRequest.getSignature(); - String txnId = String.valueOf(payloadMap.getOrDefault("txnId", payloadMap.getOrDefault("txn_id", ""))); - String orderNo = String.valueOf(payloadMap.getOrDefault("orderNo", payloadMap.getOrDefault("order_no", ""))); - String txnStatus = String.valueOf(payloadMap.getOrDefault("txnStatus", payloadMap.getOrDefault("txn_status", payloadMap.getOrDefault("status", "")))); + LOGGER.debug("[{}] received callback mid={} signaturePresent={}", apiName, mid, signature != null); - // Compute checksum: Mid + txnId + orderNo + txnStatus - String checksumStr = mid + txnId + orderNo + txnStatus; - String computedChecksum = encryptionUtil.getChecksumCRC32(checksumStr); + // Decrypt data + String decrypted = encryptionUtil.decryptMData(encryptedData,paymentEncryKey); + if (decrypted == null) { + LOGGER.error("[{}] Failed to decrypt callback payload for mid={}", apiName, mid); + return new ResponseModel(BBPSConstant.MSG_DECRYPTION_FAILED, String.valueOf(BBPSConstant.STATUS_CODE_207), BBPSConstant.HTTP_OK_INT, null); + } - boolean signatureMatches = false; - if (computedChecksum != null && signature != null) { - signatureMatches = computedChecksum.equals(signature) || computedChecksum.equals(String.valueOf(Long.parseLong(signature))); - } + LOGGER.info("[{}] Decrypted data for mid={}", apiName, mid); - // Find DB record - Optional recordOpt = Optional.empty(); - if (orderNo != null && !orderNo.isEmpty()) { - recordOpt = paymentStartRecordRepository.findByOrderNo(orderNo); - } - if (recordOpt.isEmpty() && txnId != null && !txnId.isEmpty()) { - recordOpt = paymentStartRecordRepository.findByTxnId(txnId); - } + // Parse decrypted JSON + Map payloadMap; + try { + payloadMap = objectMapper.readValue(decrypted, new TypeReference>(){}); + } catch (JsonProcessingException e) { + LOGGER.error("[{}] Failed to parse decrypted callback JSON: {}", apiName, e.getMessage(), e); + return new ResponseModel(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, String.valueOf(BBPSConstant.STATUS_CODE_207), BBPSConstant.HTTP_OK_INT, null); + } - PaymentStartRecord record = recordOpt.orElse(null); - if (record == null) { - LOGGER.warn("No PaymentStartRecord found for orderNo={} txnId={}", orderNo, txnId); - // still return 205 to acknowledge receipt - return new ResponseModel("No matching record", "205", 200, null); - } + String txnId = String.valueOf(payloadMap.getOrDefault("txnId", payloadMap.getOrDefault("txn_id", ""))); + String orderNo = String.valueOf(payloadMap.getOrDefault("orderNo", payloadMap.getOrDefault("order_no", ""))); + String txnStatus = String.valueOf(payloadMap.getOrDefault("txnStatus", payloadMap.getOrDefault("txn_status", payloadMap.getOrDefault("status", "")))); - // Update record with bank response and signature - record.setBankResponse(decrypted); - record.setSignature(signature); - // update txn status and callback timestamp - record.setTxnStatus(txnStatus); - record.setCallbackReceivedOn(java.time.LocalDateTime.now().toString()); + LOGGER.debug("[{}] parsed callback txnId={} orderNo={} txnStatus={}", apiName, txnId, orderNo, txnStatus); - String merchantStatus; - if (!signatureMatches) { - LOGGER.error("Signature mismatch. computed={} provided={}", computedChecksum, signature); - record.setCallbackStatus("SIGNATURE_MISMATCH"); - paymentStartRecordRepository.save(record); - merchantStatus = "207"; // treat as rejected/failure - LOGGER.info("[{}] END | returning status={}", apiName, merchantStatus); - return new ResponseModel("Signature mismatch", merchantStatus, 200, null); - } + // Compute checksum: Mid + txnId + orderNo + txnStatus + String checksumStr = mid + txnId + orderNo + txnStatus; + String computedChecksum = encryptionUtil.getChecksumCRC32(checksumStr); - // signature ok - decide response based on txnStatus - if (txnStatus.equalsIgnoreCase("SUCCESS") || txnStatus.equalsIgnoreCase("SUCCESSFUL") || txnStatus.equalsIgnoreCase("SUCCESSFULL")) { - record.setCallbackStatus("SUCCESS"); - paymentStartRecordRepository.save(record); - merchantStatus = "206"; // accepted by merchant - } else if (txnStatus.equalsIgnoreCase("FAILED") || txnStatus.equalsIgnoreCase("FAIL")) { - record.setCallbackStatus("FAILED"); - paymentStartRecordRepository.save(record); - merchantStatus = "207"; // rejected - } else { - record.setCallbackStatus("RECEIVED"); - paymentStartRecordRepository.save(record); - merchantStatus = "205"; // generic ack - } + boolean signatureMatches = false; + if (computedChecksum != null && signature != null) { + signatureMatches = computedChecksum.equals(signature) || computedChecksum.equals(String.valueOf(Long.parseLong(signature))); + } + + LOGGER.debug("[{}] checksum computed={} provided={} matches={}", apiName, computedChecksum, signature, signatureMatches); + + // Find DB record + Optional recordOpt = Optional.empty(); + if (orderNo != null && !orderNo.isEmpty()) { + recordOpt = paymentStartRecordRepository.findByOrderNo(orderNo); + LOGGER.debug("[{}] looked up by orderNo={} found={}", apiName, orderNo, recordOpt.isPresent()); + } + if (recordOpt.isEmpty() && txnId != null && !txnId.isEmpty()) { + recordOpt = paymentStartRecordRepository.findByTxnId(txnId); + LOGGER.debug("[{}] looked up by txnId={} found={}", apiName, txnId, recordOpt.isPresent()); + } + + PaymentStartRecord record = recordOpt.orElse(null); + if (record == null) { + LOGGER.warn("[{}] No PaymentStartRecord found for orderNo={} txnId={}", apiName, orderNo, txnId); + // still return 205 to acknowledge receipt + return new ResponseModel(BBPSConstant.MSG_NO_MATCHING_RECORD, BBPSConstant.MERCHANT_ACK_NO_MATCH, BBPSConstant.HTTP_OK_INT, null); + } + + // Update record with bank response and signature + record.setBankResponse(decrypted); + record.setSignature(signature); + // update txn status and callback timestamp + record.setTxnStatus(txnStatus); + record.setCallbackReceivedOn(java.time.LocalDateTime.now().toString()); + + String merchantStatus; + if (!signatureMatches) { + LOGGER.error("[{}] Signature mismatch. computed={} provided={}", apiName, computedChecksum, signature); + record.setCallbackStatus(BBPSConstant.SIGNATURE_MISMATCH); + paymentStartRecordRepository.save(record); + merchantStatus = BBPSConstant.MERCHANT_ACK_REJECT; // treat as rejected/failure + LOGGER.info("[{}] END | returning status={}", apiName, merchantStatus); + return new ResponseModel(BBPSConstant.MSG_SIGNATURE_MISMATCH, merchantStatus, BBPSConstant.HTTP_OK_INT, null); + } + + // signature ok - decide response based on txnStatus + if (txnStatus.equalsIgnoreCase("SUCCESS") || txnStatus.equalsIgnoreCase("SUCCESSFUL") || txnStatus.equalsIgnoreCase("SUCCESSFULL")) { + record.setCallbackStatus(BBPSConstant.CALLBACK_STATUS_SUCCESS); + paymentStartRecordRepository.save(record); + merchantStatus = BBPSConstant.MERCHANT_ACK_OK; // accepted by merchant + LOGGER.info("[{}] Callback success persisted orderNo={} txnId={}", apiName, orderNo, txnId); + } else if (txnStatus.equalsIgnoreCase("FAILED") || txnStatus.equalsIgnoreCase("FAIL")) { + record.setCallbackStatus(BBPSConstant.CALLBACK_STATUS_FAILED); + paymentStartRecordRepository.save(record); + merchantStatus = BBPSConstant.MERCHANT_ACK_REJECT; // rejected + LOGGER.info("[{}] Callback failure persisted orderNo={} txnId={}", apiName, orderNo, txnId); + } else { + record.setCallbackStatus(BBPSConstant.CALLBACK_STATUS_RECEIVED); + paymentStartRecordRepository.save(record); + merchantStatus = BBPSConstant.MERCHANT_ACK_NO_MATCH; // generic ack + LOGGER.info("[{}] Callback received persisted orderNo={} txnId={} status={}", apiName, orderNo, txnId, txnStatus); + } + + LOGGER.info("[{}] END", apiName); + + return new ResponseModel(BBPSConstant.MSG_CALLBACK_PROCESSED, merchantStatus, BBPSConstant.HTTP_OK_INT, null); + } - LOGGER.info("Callback processed for orderNo={} txnId={} status={} returning merchantStatus={}", orderNo, txnId, txnStatus, merchantStatus); - LOGGER.info("[{}] END", apiName); - return new ResponseModel("Callback processed", merchantStatus, 200, null); - } - - public Map parseHtmlResponse(String html) { Map map = new HashMap<>(); @@ -492,8 +426,10 @@ public class PaymentStartServiceImpl implements PaymentStartService { if (amount != null) map.put("amount", amount.attr("value")); if (mid != null) map.put("mid", mid.attr("value")); + LOGGER.debug("[parseHtmlResponse] parsed txnId={} amount={} mid={}", map.get("txnId"), map.get("amount"), map.get("mid")); + } catch (Exception e) { - e.printStackTrace(); + LOGGER.error("[parseHtmlResponse] error parsing HTML: {}", e.getMessage(), e); } return map; diff --git a/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java b/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java index 36f1c6f..bf8d94d 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java +++ b/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java @@ -141,7 +141,44 @@ public class BBPSConstant { public static final String RESPONSE_GENERAL_RESP = "generalResp"; public static final String SUCCESS_RESPONSE = "SUCCESS"; - // Newly added constants used by ComplainServiceImpl + // Newly added constants for Payment integration (avoid magic strings in service + // code) + public static final String UAT_PAYMENT_URL_WEB = "https://uat.godemo.in/api/UATIntegration/uat/payin/paymentrequest"; + public static final String UAT_PAYMENT_URL_INTENT = "https://uat.godemo.in/api/UATIntegration/paymentrequest/seamless"; + public static final String RESP_CODE_KEY = "respCode"; // key used by HttpService responses + + public static final String RESPONSE_TYPE_HTML = "HTML"; + public static final String RESPONSE_TYPE_QR = "QR"; + public static final String RESPONSE_TYPE_JSON = "JSON"; + + public static final String API_START_PAYMENT = "StartPayment"; + public static final String WARNING_STATUS = "WARNING"; + public static final String SIGNATURE_MISMATCH = "SIGNATURE_MISMATCH"; + + // Additional constants to remove magic values from service implementation + public static final String ORDER_PREFIX = "ORD"; + public static final String DEFAULT_DATE_OF_REG = "2025-12-31"; + public static final String TXN_REQ_TYPE = "S"; + + // Common numeric status codes used by payment provider + public static final int STATUS_CODE_205 = 205; + public static final int STATUS_CODE_206 = 206; + public static final int STATUS_CODE_207 = 207; + public static final int STATUS_CODE_1003 = 1003; + + // Callback status labels (preserve existing uppercase values expected in DB) + public static final String CALLBACK_STATUS_SUCCESS = "SUCCESS"; + public static final String CALLBACK_STATUS_FAILED = "FAILED"; + public static final String CALLBACK_STATUS_RECEIVED = "RECEIVED"; + + // Merchant acknowledgement codes returned to PG/callback + public static final String MERCHANT_ACK_OK = "206"; + public static final String MERCHANT_ACK_NO_MATCH = "205"; + public static final String MERCHANT_ACK_REJECT = "207"; + + public static final String HTML_RESPONSE_MESSAGE = "HTML Response Received"; + + // Restore constants used by TxnHistoryServiceImpl and ComplainServiceImpl public static final String DB_OPTIONS = "/db/options"; // used as a logical URL/identifier for master options public static final String NULL_DATA_FROM_DB = "NULL_DATA_FROM_DB"; public static final String NO_MASTER_FIELDS_FOUND = "No master fields found"; @@ -152,6 +189,20 @@ public class BBPSConstant { public static final String USER_ID_IS_EMPTY = "User id is empty"; public static final String USER_Txn_IS_NOT_FOUND = "Not Txn Found This Txn Id"; + // Common user-facing and internal message constants used by payment flow + public static final String MSG_UPSTREAM_HTTP_ERROR = "Upstream HTTP error"; + public static final String MSG_EMPTY_RESPONSE_PROVIDER = "Empty response from payment provider"; + public static final String MSG_EMPTY_RESPONSE_PG = "Empty response from PG"; + public static final String MSG_INVALID_JSON_FROM_API = "Invalid JSON received from payment API"; + public static final String MSG_TXN_PROCESSING = "Transaction processing"; + public static final String MSG_WARNING_FROM_MERCHANT = "Warning from merchant"; + public static final String MSG_FRAUD_REJECTED = "Fraud Rejected"; + public static final String MSG_DECRYPTION_FAILED = "Decryption failed"; + public static final String MSG_INVALID_DECRYPTED_JSON = "Invalid decrypted JSON"; + public static final String MSG_NO_MATCHING_RECORD = "No matching record"; + public static final String MSG_SIGNATURE_MISMATCH = "Signature mismatch"; + public static final String MSG_CALLBACK_PROCESSED = "Callback processed"; + // Timing/delay constants (milliseconds) public static final long BILL_FETCH_DELAY_MS = 5000L; // used after initiate bill fetch public static final long BILL_VALIDATE_DELAY_MS = 12000L; // used after initiate bill validate @@ -161,4 +212,5 @@ public class BBPSConstant { public static final String mid = "MIDBBPS001THA"; public static final String callbackurl = "https://11.godemo.in/BBPS/payment/callback/startPayment"; public static final String companyName = "testing.com"; + } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d72e0a0..a189cc7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -54,6 +54,12 @@ ekyc.secreteKey= secret_live_b2acae6e1fd54d6faa148b08af8ae098 ekyc.mobile=https://ekyc.lincpay.in/EKYC/ekyc/mobile/generate-otp ekyc.mobile.verify.otp=https://ekyc.lincpay.in/EKYC/ekyc/verify-mobile-otp +payment.enckey = g5c4lr0tgjjqhu0bfgmhjf26ab0z8ktk +payment.mid = MIDBBPS001THA +payment.callbackurl = https://11.godemo.in/BBPS/payment/callback/startPayment +payment.companyName = testing.com + + # throw NoHandlerFoundException for 404s spring.mvc.throw-exception-if-no-handler-found=true