imroved the logic of intentpaymentapi
This commit is contained in:
+223
-295
@@ -48,9 +48,6 @@ 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";
|
||||
|
||||
private final PaymentStartRecordRepository paymentStartRecordRepository;
|
||||
private final HttpService httpService;
|
||||
private final EncryptionUtil encryptionUtil;
|
||||
@@ -79,173 +76,85 @@ 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("[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(BBPSConstant.enckey);
|
||||
String plaintext = helper.convertObjectToJson(sent);
|
||||
String encKey = BBPSConstant.enckey;
|
||||
sent.setEnckey(encKey);
|
||||
|
||||
String encrypted = encryptionUtil.encryptMData(plaintext, encKey);
|
||||
String encrypted = encryptionUtil.encryptMData(plaintext, BBPSConstant.enckey);
|
||||
|
||||
PaymentRequestPayload finalPayload = new PaymentRequestPayload();
|
||||
finalPayload.setMid(BBPSConstant.mid);
|
||||
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);
|
||||
|
||||
// --- CALL EXTERNAL API ---
|
||||
Map<String, String> 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<String, String> 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<String, Object> top = objectMapper.readValue(respBody, new TypeReference<Map<String, Object>>(){});
|
||||
|
||||
// Pick inner payload if present
|
||||
Map<String, Object> payload = top;
|
||||
if (top.containsKey("data") && top.get("data") instanceof Map) {
|
||||
//noinspection unchecked
|
||||
payload = (Map<String, Object>) 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<String, Object> 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 +162,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 +178,12 @@ public class PaymentStartServiceImpl implements PaymentStartService {
|
||||
sent.setEmailId(request.getEmailId());
|
||||
sent.setUserId(request.getMid());
|
||||
sent.setCustomerVpa(request.getCustomerVpa());
|
||||
sent.setDateOfReg("2025-12-31");
|
||||
sent.setDateOfReg(BBPSConstant.DEFAULT_DATE_OF_REG);
|
||||
String effectiveMid = (request.getMid() != null && !request.getMid().isEmpty()) ? request.getMid() : BBPSConstant.mid;
|
||||
sent.setMid(BBPSConstant.mid);
|
||||
sent.setMobileNo(request.getMobileNo());
|
||||
sent.setRespUrl(BBPSConstant.callbackurl);
|
||||
sent.setTxnReqType("S");
|
||||
sent.setTxnReqType(BBPSConstant.TXN_REQ_TYPE);
|
||||
sent.setName(request.getName());
|
||||
sent.setUdf1(request.getUdf1());
|
||||
sent.setUdf2(request.getUdf2());
|
||||
@@ -292,192 +199,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 = BBPSConstant.enckey;
|
||||
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<String, String> 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<String, String> parsedHtml = parseHtmlResponse(respBody);
|
||||
*
|
||||
* for(Map.Entry<String, String> 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(BBPSConstant.mid, 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<String, String> 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<String, String> 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<String, String> 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<String, Object> payloadMap;
|
||||
try {
|
||||
payloadMap = objectMapper.readValue(decrypted, new TypeReference<Map<String, Object>>(){});
|
||||
} 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, BBPSConstant.enckey);
|
||||
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<PaymentStartRecord> 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<String, Object> payloadMap;
|
||||
try {
|
||||
payloadMap = objectMapper.readValue(decrypted, new TypeReference<Map<String, Object>>(){});
|
||||
} 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<PaymentStartRecord> 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<String, String> parseHtmlResponse(String html) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
|
||||
@@ -492,8 +418,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;
|
||||
|
||||
@@ -141,7 +141,43 @@ 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";
|
||||
@@ -150,6 +186,20 @@ public class BBPSConstant {
|
||||
public static final String SOMETHING_WENT_WRONG = "Something went wrong";
|
||||
public static final String USER_ID_IS_EMPTY = "User id is empty";
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user