working web based and intent
This commit is contained in:
@@ -59,22 +59,7 @@ public class PaymentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@PostMapping(value="/callback/paymentIntiate", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
||||||
public ResponseEntity<ResponseModel> handleBillValidateCallback(@RequestBody TransactionStatusResponse transactionStatusResponse) {
|
|
||||||
ResponseModel genralResp=paymentService.handlePaymentIntiateCallback(transactionStatusResponse);
|
|
||||||
// Return a response indicating successful receipt
|
|
||||||
return ResponseEntity.ok(genralResp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(value = "/startPayment", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
||||||
public ResponseEntity<ResponseModel> startPayment(@Validated @RequestBody PaymentStartUserRequest paymentStartUserRequest) {
|
|
||||||
if (this.paymentStartService == null) {
|
|
||||||
// Defensive: if bean not injected, return failure
|
|
||||||
return ResponseEntity.badRequest().body(new ResponseModel("PaymentStartService not available", "FAILED", 400, null));
|
|
||||||
}
|
|
||||||
ResponseModel response = paymentStartService.startPayment(paymentStartUserRequest);
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
package com.billpaymentservice.bbps.controller;
|
package com.billpaymentservice.bbps.controller;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import com.billpaymentservice.bbps.dto.ResponseModel;
|
import com.billpaymentservice.bbps.dto.ResponseModel;
|
||||||
import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest;
|
import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest;
|
||||||
|
import com.billpaymentservice.bbps.dto.request.CallbackRequest;
|
||||||
import com.billpaymentservice.bbps.service.PaymentStartService;
|
import com.billpaymentservice.bbps.service.PaymentStartService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/payment")
|
||||||
public class PaymentStartController {
|
public class PaymentStartController {
|
||||||
private final PaymentStartService paymentStartService;
|
private final PaymentStartService paymentStartService;
|
||||||
|
|
||||||
@@ -20,12 +26,28 @@ public class PaymentStartController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/startWebPayment", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
@PostMapping(value = "/startWebPayment", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public ResponseEntity<ResponseModel> startPayment(@Validated @RequestBody PaymentStartUserRequest paymentStartUserRequest) {
|
public ResponseEntity<ResponseModel> startWebPayment(@Validated @RequestBody PaymentStartUserRequest paymentStartUserRequest) {
|
||||||
|
|
||||||
|
ResponseModel response = paymentStartService.startWebPayment(paymentStartUserRequest);
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/callback/startPayment", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Map<String, String>> handleCallback(@RequestBody CallbackRequest callbackRequest) {
|
||||||
|
ResponseModel resp = paymentStartService.handleStartCallback(callbackRequest);
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
// Use the status field from ResponseModel as merchant status code string
|
||||||
|
result.put("status", resp.getStatus());
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/startIntentPayment", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ResponseModel> startIntentPayment(@Validated @RequestBody PaymentStartUserRequest paymentStartUserRequest) {
|
||||||
if (this.paymentStartService == null) {
|
if (this.paymentStartService == null) {
|
||||||
// Defensive: if bean not injected, return failure
|
// Defensive: if bean not injected, return failure
|
||||||
return ResponseEntity.badRequest().body(new ResponseModel("PaymentStartService not available", "FAILED", 400, null));
|
return ResponseEntity.badRequest().body(new ResponseModel("PaymentStartService not available", "FAILED", 400, null));
|
||||||
}
|
}
|
||||||
ResponseModel response = paymentStartService.startWebPayment(paymentStartUserRequest);
|
ResponseModel response = paymentStartService.startIntentPayment(paymentStartUserRequest);
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.billpaymentservice.bbps.dto.request;
|
||||||
|
|
||||||
|
public class CallbackRequest {
|
||||||
|
private String mid;
|
||||||
|
private String data;
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
public String getMid() {
|
||||||
|
return mid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMid(String mid) {
|
||||||
|
this.mid = mid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(String data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignature(String signature) {
|
||||||
|
this.signature = signature;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,4 +52,30 @@ public class PaymentStartRecord {
|
|||||||
|
|
||||||
@Column(name = "created_on", nullable = false)
|
@Column(name = "created_on", nullable = false)
|
||||||
private String createdOn;
|
private String createdOn;
|
||||||
}
|
|
||||||
|
// New callback-related fields
|
||||||
|
@Column(name = "callback_status")
|
||||||
|
private String callbackStatus;
|
||||||
|
|
||||||
|
@Column(name = "bank_response", columnDefinition = "TEXT")
|
||||||
|
private String bankResponse;
|
||||||
|
|
||||||
|
@Column(name = "signature")
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
// Store original request body and full response body
|
||||||
|
@Column(name = "request_body", columnDefinition = "TEXT")
|
||||||
|
private String requestBody;
|
||||||
|
|
||||||
|
@Column(name = "response_body", columnDefinition = "TEXT")
|
||||||
|
private String responseBody;
|
||||||
|
|
||||||
|
@Column(name = "response_type")
|
||||||
|
private String responseType; // e.g., "QR", "HTML", "JSON"
|
||||||
|
|
||||||
|
@Column(name = "txn_status")
|
||||||
|
private String txnStatus;
|
||||||
|
|
||||||
|
@Column(name = "callback_received_on")
|
||||||
|
private String callbackReceivedOn;
|
||||||
|
}
|
||||||
+6
-1
@@ -1,5 +1,7 @@
|
|||||||
package com.billpaymentservice.bbps.repository;
|
package com.billpaymentservice.bbps.repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@@ -7,4 +9,7 @@ import com.billpaymentservice.bbps.entites.PaymentStartRecord;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PaymentStartRecordRepository extends JpaRepository<PaymentStartRecord, Long> {
|
public interface PaymentStartRecordRepository extends JpaRepository<PaymentStartRecord, Long> {
|
||||||
}
|
Optional<PaymentStartRecord> findByOrderNo(String orderNo);
|
||||||
|
Optional<PaymentStartRecord> findByTxnId(String txnId);
|
||||||
|
Optional<PaymentStartRecord> findByRequestId(String requestId);
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ public interface PaymentService {
|
|||||||
|
|
||||||
ResponseModel checkTrasactionStatusByClientReqId(TrasactionStatusByClientReqIdRequest transactionStatusRequest);
|
ResponseModel checkTrasactionStatusByClientReqId(TrasactionStatusByClientReqIdRequest transactionStatusRequest);
|
||||||
|
|
||||||
ResponseModel handlePaymentIntiateCallback(TransactionStatusResponse transactionStatusResponse);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package com.billpaymentservice.bbps.service;
|
|||||||
|
|
||||||
import com.billpaymentservice.bbps.dto.ResponseModel;
|
import com.billpaymentservice.bbps.dto.ResponseModel;
|
||||||
import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest;
|
import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest;
|
||||||
|
import com.billpaymentservice.bbps.dto.request.CallbackRequest;
|
||||||
|
|
||||||
public interface PaymentStartService {
|
public interface PaymentStartService {
|
||||||
ResponseModel startPayment(PaymentStartUserRequest request);
|
|
||||||
|
|
||||||
ResponseModel startWebPayment(PaymentStartUserRequest request);
|
ResponseModel startWebPayment(PaymentStartUserRequest request);
|
||||||
}
|
|
||||||
|
ResponseModel handleStartCallback(CallbackRequest callbackRequest);
|
||||||
|
|
||||||
|
ResponseModel startIntentPayment(PaymentStartUserRequest paymentStartUserRequest);
|
||||||
|
}
|
||||||
@@ -393,22 +393,7 @@ public class PaymentServiceImpl implements PaymentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ResponseModel handlePaymentIntiateCallback(TransactionStatusResponse transactionStatusResponse) {
|
|
||||||
String apiName = "HANDLE_PAYMENT_INITIATE_CALLBACK";
|
|
||||||
LOGGER.info("[{}] START | payload={} ", apiName, transactionStatusResponse);
|
|
||||||
LOGGER.info("Received Call Back: {}", transactionStatusResponse);
|
|
||||||
if (transactionStatusResponse.getGeneralResp().getStatusCode().equals(BBPSConstant.SUCCESS_CODE_STR)) {
|
|
||||||
LOGGER.info("Callback indicates SUCCESS with status={}", transactionStatusResponse.getGeneralResp().getStatus());
|
|
||||||
} else {
|
|
||||||
LOGGER.info("Callback indicates FAILURE with status={}", transactionStatusResponse.getGeneralResp().getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("[{}] END | callback processed", apiName);
|
|
||||||
|
|
||||||
return new ResponseModel(BBPSConstant.CALLBACK_RECEIVED_SUCCESS,
|
|
||||||
BBPSConstant.SUCCESS_CODE_STR,
|
|
||||||
BBPSConstant.HTTP_OK_INT,
|
|
||||||
"");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+136
-33
@@ -6,6 +6,7 @@ import java.util.Map;
|
|||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -17,6 +18,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import com.billpaymentservice.bbps.dto.PaymentStartReturnDto;
|
import com.billpaymentservice.bbps.dto.PaymentStartReturnDto;
|
||||||
import com.billpaymentservice.bbps.dto.ResponseModel;
|
import com.billpaymentservice.bbps.dto.ResponseModel;
|
||||||
import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest;
|
import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest;
|
||||||
|
import com.billpaymentservice.bbps.dto.request.CallbackRequest;
|
||||||
import com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse;
|
import com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse;
|
||||||
import com.billpaymentservice.bbps.dto.response.PaymentStatusResponse;
|
import com.billpaymentservice.bbps.dto.response.PaymentStatusResponse;
|
||||||
import com.billpaymentservice.bbps.dto.sentRequest.PaymentDetailSentRequest;
|
import com.billpaymentservice.bbps.dto.sentRequest.PaymentDetailSentRequest;
|
||||||
@@ -36,13 +38,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.billpaymentservice.bbps.entites.OutboundTraceLog;
|
import com.billpaymentservice.bbps.entites.OutboundTraceLog;
|
||||||
import com.billpaymentservice.bbps.entites.BharatConnectMaster;
|
import com.billpaymentservice.bbps.entites.BharatConnectMaster;
|
||||||
|
import com.billpaymentservice.bbps.entites.PaymentStartRecord;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PaymentStartServiceImpl implements PaymentStartService {
|
public class PaymentStartServiceImpl implements PaymentStartService {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(PaymentStartServiceImpl.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(PaymentStartServiceImpl.class);
|
||||||
|
|
||||||
private String uatPaymentUrl="https://uat.godemo.in/api/UATIntegration/uat/payin/paymentrequest";
|
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 PaymentStartRecordRepository paymentStartRecordRepository;
|
||||||
private final HttpService httpService;
|
private final HttpService httpService;
|
||||||
@@ -69,7 +73,7 @@ public class PaymentStartServiceImpl implements PaymentStartService {
|
|||||||
this.bbpsMapperHelper = bbpsMapperHelper;
|
this.bbpsMapperHelper = bbpsMapperHelper;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public ResponseModel startPayment(PaymentStartUserRequest request) {
|
public ResponseModel startIntentPayment(PaymentStartUserRequest request) {
|
||||||
|
|
||||||
String requestId = request.getReqId();
|
String requestId = request.getReqId();
|
||||||
String apiName = "StartPayment";
|
String apiName = "StartPayment";
|
||||||
@@ -97,15 +101,17 @@ public class PaymentStartServiceImpl implements PaymentStartService {
|
|||||||
PaymentRequestPayload finalPayload = new PaymentRequestPayload();
|
PaymentRequestPayload finalPayload = new PaymentRequestPayload();
|
||||||
finalPayload.setMid(BBPSConstant.mid);
|
finalPayload.setMid(BBPSConstant.mid);
|
||||||
finalPayload.setPayload(encrypted);
|
finalPayload.setPayload(encrypted);
|
||||||
|
|
||||||
|
|
||||||
String forwardJson = helper.convertObjectToJson(finalPayload);
|
String forwardJson = helper.convertObjectToJson(finalPayload);
|
||||||
|
LOGGER.info("Payment Start Request Payload: " + forwardJson);
|
||||||
OutboundTraceLog outboundLog =
|
OutboundTraceLog outboundLog =
|
||||||
helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrl,
|
helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrlintent,
|
||||||
BBPSConstant.HTTP_POST, forwardJson);
|
BBPSConstant.HTTP_POST, forwardJson);
|
||||||
|
|
||||||
// --- CALL EXTERNAL API ---
|
// --- CALL EXTERNAL API ---
|
||||||
Map<String, String> responseMap =
|
Map<String, String> responseMap =
|
||||||
httpService.postRequest(uatPaymentUrl, forwardJson, null);
|
httpService.postRequest(uatPaymentUrlintent, forwardJson, null);
|
||||||
|
|
||||||
LOGGER.info("Payment Start Response: " + responseMap);
|
LOGGER.info("Payment Start Response: " + responseMap);
|
||||||
|
|
||||||
@@ -166,19 +172,7 @@ public class PaymentStartServiceImpl implements PaymentStartService {
|
|||||||
|
|
||||||
String message = String.valueOf(payload.getOrDefault("message", top.getOrDefault("message", "")));
|
String message = String.valueOf(payload.getOrDefault("message", top.getOrDefault("message", "")));
|
||||||
|
|
||||||
// Map outcome codes according to provided table:
|
|
||||||
// Success: 200
|
|
||||||
// Success without warnings: 205
|
|
||||||
// Warning -> success (fraud accepted): 206 (treat as success)
|
|
||||||
// Warning -> failed (fraud rejected): 207 (treat as failure)
|
|
||||||
// Transaction processing: 198 (treat as pending/processing)
|
|
||||||
// Fail/Exception: 417 -> failure
|
|
||||||
// Failed: 99 -> failure
|
|
||||||
// Rejected by acquirer: 197 -> failure
|
|
||||||
// Declined: 194 -> failure
|
|
||||||
// Auth success but recurring error: 193 -> failure
|
|
||||||
// Warning: 1003 -> warning (non-fatal)
|
|
||||||
|
|
||||||
// Handle special cases
|
// Handle special cases
|
||||||
if (outcomeCode == 200 || outcomeCode == 205 || outcomeCode == 206) {
|
if (outcomeCode == 200 || outcomeCode == 205 || outcomeCode == 206) {
|
||||||
// success - build apiResponse from wrapper or direct payload
|
// success - build apiResponse from wrapper or direct payload
|
||||||
@@ -238,7 +232,15 @@ public class PaymentStartServiceImpl implements PaymentStartService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save record
|
// Save record
|
||||||
bbpsMapperHelper.savePaymentStartRecord(request, apiResponse, txnId, orderNo);
|
// 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";
|
||||||
|
}
|
||||||
|
bbpsMapperHelper.savePaymentStartRecord(request, apiResponse, txnId, orderNo, requestJson, rawResponse, responseType);
|
||||||
|
|
||||||
// Prepare DTO
|
// Prepare DTO
|
||||||
PaymentStartReturnDto responseData = new PaymentStartReturnDto();
|
PaymentStartReturnDto responseData = new PaymentStartReturnDto();
|
||||||
@@ -322,12 +324,12 @@ public class PaymentStartServiceImpl implements PaymentStartService {
|
|||||||
"&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8);
|
"&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8);
|
||||||
LOGGER.info("Form Payload: {}", formPayload);
|
LOGGER.info("Form Payload: {}", formPayload);
|
||||||
OutboundTraceLog outboundLog =
|
OutboundTraceLog outboundLog =
|
||||||
helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrl,
|
helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrlweb,
|
||||||
BBPSConstant.HTTP_POST, formPayload);
|
BBPSConstant.HTTP_POST, formPayload);
|
||||||
|
|
||||||
// ----------- CALL API (FORM URL-ENCODED) -----------
|
// ----------- CALL API (FORM URL-ENCODED) -----------
|
||||||
Map<String, String> responseMap =
|
Map<String, String> responseMap =
|
||||||
httpService.postFormRequest(uatPaymentUrl, formPayload);
|
httpService.postFormRequest(uatPaymentUrlweb, formPayload);
|
||||||
|
|
||||||
LOGGER.info("Payment Start Response: {}", responseMap);
|
LOGGER.info("Payment Start Response: {}", responseMap);
|
||||||
|
|
||||||
@@ -350,21 +352,122 @@ public class PaymentStartServiceImpl implements PaymentStartService {
|
|||||||
|
|
||||||
// ❗❗ PG RETURNS HTML → NO JSON PARSING REQUIRED
|
// ❗❗ PG RETURNS HTML → NO JSON PARSING REQUIRED
|
||||||
helper.updateInboundLogSuccess(inboundLog, respBody, "200");
|
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);
|
||||||
|
|
||||||
// Save record with HTML (optional)
|
// Return HTML directly in API response
|
||||||
// bbpsMapperHelper.savePaymentStartHtmlRecord(request, respBody, txnId, orderNo);
|
Map<String, String> result = new HashMap<>();
|
||||||
|
result.put("html", respBody);
|
||||||
|
|
||||||
// Return HTML directly in API response
|
return ResponseModel.builder()
|
||||||
Map<String, String> result = new HashMap<>();
|
.status("SUCCESS")
|
||||||
result.put("html", respBody);
|
.statusCode(200)
|
||||||
|
.message("HTML Response Received")
|
||||||
return ResponseModel.builder()
|
.data(result)
|
||||||
.status("SUCCESS")
|
.build();
|
||||||
.statusCode(200)
|
|
||||||
.message("HTML Response Received")
|
|
||||||
.data(result)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseModel handleStartCallback(CallbackRequest callbackRequest) {
|
||||||
|
String apiName = "HANDLE_START_CALLBACK";
|
||||||
|
LOGGER.info("[{}] START | payload={}", apiName, callbackRequest);
|
||||||
|
|
||||||
}
|
if (callbackRequest == null) {
|
||||||
|
LOGGER.warn("Callback payload is null");
|
||||||
|
return new ResponseModel("Invalid callback", "205", 200, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String mid = callbackRequest.getMid();
|
||||||
|
String encryptedData = callbackRequest.getData();
|
||||||
|
String signature = callbackRequest.getSignature();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Decrypted callback data: {}", decrypted);
|
||||||
|
|
||||||
|
// 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 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", ""))));
|
||||||
|
|
||||||
|
// Compute checksum: Mid + txnId + orderNo + txnStatus
|
||||||
|
String checksumStr = mid + txnId + orderNo + txnStatus;
|
||||||
|
String computedChecksum = encryptionUtil.getChecksumCRC32(checksumStr);
|
||||||
|
|
||||||
|
boolean signatureMatches = false;
|
||||||
|
if (computedChecksum != null && signature != null) {
|
||||||
|
signatureMatches = computedChecksum.equals(signature) || computedChecksum.equals(String.valueOf(Long.parseLong(signature)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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={}", 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1081,18 +1081,25 @@ public class BillerMapperHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void savePaymentStartRecord(PaymentStartUserRequest request, PaymentStartApiResponse apiResponse,
|
public void savePaymentStartRecord(PaymentStartUserRequest request, PaymentStartApiResponse apiResponse,
|
||||||
String txnId, String orderNo) {
|
String txnId, String orderNo, String requestJson, String rawResponse, String responseType) {
|
||||||
PaymentStartRecord record = new PaymentStartRecord();
|
PaymentStartRecord record = new PaymentStartRecord();
|
||||||
record.setOrderNo(orderNo);
|
record.setOrderNo(orderNo);
|
||||||
record.setRequestId(request.getReqId());
|
record.setRequestId(request.getReqId());
|
||||||
record.setClientMid(request.getMid());
|
record.setClientMid(request.getMid());
|
||||||
record.setClientUserId(request.getClientUserId());
|
record.setClientUserId(request.getClientUserId());
|
||||||
record.setInternalTxnId(txnId);
|
record.setInternalTxnId(txnId);
|
||||||
record.setAmount(apiResponse.getResponseData().getAmount());
|
if (apiResponse != null && apiResponse.getResponseData() != null) {
|
||||||
record.setMid(apiResponse.getResponseData().getMid());
|
record.setAmount(apiResponse.getResponseData().getAmount());
|
||||||
record.setTxnId(apiResponse.getResponseData().getTxnId());
|
record.setMid(apiResponse.getResponseData().getMid());
|
||||||
record.setQrString(apiResponse.getResponseData().getQrString());
|
record.setTxnId(apiResponse.getResponseData().getTxnId());
|
||||||
record.setCreatedOn(apiResponse.getResponseData().getCreatedOn());
|
record.setQrString(apiResponse.getResponseData().getQrString());
|
||||||
|
record.setCreatedOn(apiResponse.getResponseData().getCreatedOn());
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the original request body and raw response
|
||||||
|
record.setRequestBody(requestJson);
|
||||||
|
record.setResponseBody(rawResponse);
|
||||||
|
record.setResponseType(responseType);
|
||||||
|
|
||||||
paymentStartRecordRepository.save(record);
|
paymentStartRecordRepository.save(record);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.billpaymentservice.bbps.util;
|
package com.billpaymentservice.bbps.util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
@@ -18,12 +22,9 @@ public class EncryptionUtil {
|
|||||||
/**
|
/**
|
||||||
* Encrypts plaintext using AES/CBC/PKCS5Padding.
|
* Encrypts plaintext using AES/CBC/PKCS5Padding.
|
||||||
* If the provided key length is not 16/24/32 bytes it will be adjusted to 16 bytes (padded/truncated).
|
* If the provided key length is not 16/24/32 bytes it will be adjusted to 16 bytes (padded/truncated).
|
||||||
* NOTE: This follows the algorithm snippet provided by the user but pads/truncates the key to avoid
|
|
||||||
* common runtime errors when key lengths are incorrect.
|
|
||||||
*/
|
*/
|
||||||
public String encryptMData(String plaintext, String encryptKey) {
|
public String encryptMData(String plaintext, String encryptKey) {
|
||||||
try {
|
try {
|
||||||
// Ensure key length is 16 bytes (AES-128). If callers provide a longer key this will truncate.
|
|
||||||
byte[] keyBytes = normalizeKey(encryptKey);
|
byte[] keyBytes = normalizeKey(encryptKey);
|
||||||
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
|
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
@@ -61,4 +62,56 @@ public class EncryptionUtil {
|
|||||||
}
|
}
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Decrypts data encrypted by encryptMData: Base64(iv + ciphertext) with AES/CBC/PKCS5Padding
|
||||||
|
*/
|
||||||
|
public String decryptMData(String base64Payload, String encryptKey) {
|
||||||
|
if (base64Payload == null) return null;
|
||||||
|
try {
|
||||||
|
byte[] combined = Base64.getDecoder().decode(base64Payload);
|
||||||
|
// IV is first 16 bytes for AES
|
||||||
|
if (combined.length < 16) {
|
||||||
|
LOGGER.warn("Encrypted payload too short to contain IV");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
System.arraycopy(combined, 0, iv, 0, 16);
|
||||||
|
byte[] cipherText = new byte[combined.length - 16];
|
||||||
|
System.arraycopy(combined, 16, cipherText, 0, cipherText.length);
|
||||||
|
|
||||||
|
byte[] keyBytes = normalizeKey(encryptKey);
|
||||||
|
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv));
|
||||||
|
|
||||||
|
byte[] plain = cipher.doFinal(cipherText);
|
||||||
|
return new String(plain, StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("Error decrypting payload: {}", ex.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute CRC32 checksum value for the provided input string and return the numeric value as String
|
||||||
|
*/
|
||||||
|
public String getChecksumCRC32(String input) {
|
||||||
|
if (input == null) input = "";
|
||||||
|
try (InputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) {
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int len;
|
||||||
|
while ((len = is.read(buffer)) != -1) {
|
||||||
|
crc.update(buffer, 0, len);
|
||||||
|
}
|
||||||
|
long value = crc.getValue();
|
||||||
|
LOGGER.debug("Computed CRC32: {} for input: {}", value, input);
|
||||||
|
return String.valueOf(value);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Error computing CRC32: {}", e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user