diff --git a/src/main/java/com/billpaymentservice/bbps/controller/PaymentController.java b/src/main/java/com/billpaymentservice/bbps/controller/PaymentController.java index cb3731f..326d536 100644 --- a/src/main/java/com/billpaymentservice/bbps/controller/PaymentController.java +++ b/src/main/java/com/billpaymentservice/bbps/controller/PaymentController.java @@ -59,22 +59,7 @@ public class PaymentController { } - @PostMapping(value="/callback/paymentIntiate", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity 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 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); - } } \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/controller/PaymentStartController.java b/src/main/java/com/billpaymentservice/bbps/controller/PaymentStartController.java index d9621fb..dfcb2e6 100644 --- a/src/main/java/com/billpaymentservice/bbps/controller/PaymentStartController.java +++ b/src/main/java/com/billpaymentservice/bbps/controller/PaymentStartController.java @@ -1,17 +1,23 @@ package com.billpaymentservice.bbps.controller; +import java.util.HashMap; +import java.util.Map; + import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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.CallbackRequest; import com.billpaymentservice.bbps.service.PaymentStartService; @RestController +@RequestMapping("/payment") public class PaymentStartController { private final PaymentStartService paymentStartService; @@ -20,12 +26,28 @@ public class PaymentStartController { } @PostMapping(value = "/startWebPayment", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity startPayment(@Validated @RequestBody PaymentStartUserRequest paymentStartUserRequest) { + public ResponseEntity 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> handleCallback(@RequestBody CallbackRequest callbackRequest) { + ResponseModel resp = paymentStartService.handleStartCallback(callbackRequest); + Map 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 startIntentPayment(@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.startWebPayment(paymentStartUserRequest); + ResponseModel response = paymentStartService.startIntentPayment(paymentStartUserRequest); return ResponseEntity.ok(response); } -} +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/dto/request/CallbackRequest.java b/src/main/java/com/billpaymentservice/bbps/dto/request/CallbackRequest.java new file mode 100644 index 0000000..aef1389 --- /dev/null +++ b/src/main/java/com/billpaymentservice/bbps/dto/request/CallbackRequest.java @@ -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; + } +} diff --git a/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java b/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java index fa2a17a..a40db19 100644 --- a/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java +++ b/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java @@ -52,4 +52,30 @@ public class PaymentStartRecord { @Column(name = "created_on", nullable = false) 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; +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/repository/PaymentStartRecordRepository.java b/src/main/java/com/billpaymentservice/bbps/repository/PaymentStartRecordRepository.java index 80610a3..806bd2f 100644 --- a/src/main/java/com/billpaymentservice/bbps/repository/PaymentStartRecordRepository.java +++ b/src/main/java/com/billpaymentservice/bbps/repository/PaymentStartRecordRepository.java @@ -1,5 +1,7 @@ package com.billpaymentservice.bbps.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -7,4 +9,7 @@ import com.billpaymentservice.bbps.entites.PaymentStartRecord; @Repository public interface PaymentStartRecordRepository extends JpaRepository { -} + Optional findByOrderNo(String orderNo); + Optional findByTxnId(String txnId); + Optional findByRequestId(String requestId); +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/service/PaymentService.java b/src/main/java/com/billpaymentservice/bbps/service/PaymentService.java index c932c0f..72bdb0e 100644 --- a/src/main/java/com/billpaymentservice/bbps/service/PaymentService.java +++ b/src/main/java/com/billpaymentservice/bbps/service/PaymentService.java @@ -14,7 +14,6 @@ public interface PaymentService { ResponseModel checkTrasactionStatusByClientReqId(TrasactionStatusByClientReqIdRequest transactionStatusRequest); - ResponseModel handlePaymentIntiateCallback(TransactionStatusResponse transactionStatusResponse); } diff --git a/src/main/java/com/billpaymentservice/bbps/service/PaymentStartService.java b/src/main/java/com/billpaymentservice/bbps/service/PaymentStartService.java index 86b6f0f..ba7b07e 100644 --- a/src/main/java/com/billpaymentservice/bbps/service/PaymentStartService.java +++ b/src/main/java/com/billpaymentservice/bbps/service/PaymentStartService.java @@ -2,9 +2,12 @@ package com.billpaymentservice.bbps.service; import com.billpaymentservice.bbps.dto.ResponseModel; import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest; +import com.billpaymentservice.bbps.dto.request.CallbackRequest; public interface PaymentStartService { - ResponseModel startPayment(PaymentStartUserRequest request); - ResponseModel startWebPayment(PaymentStartUserRequest request); -} + + ResponseModel handleStartCallback(CallbackRequest callbackRequest); + + ResponseModel startIntentPayment(PaymentStartUserRequest paymentStartUserRequest); +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java index 9489f0a..99585aa 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java @@ -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, - ""); - } + } \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java index 7cae5c4..ea696f7 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java @@ -6,6 +6,7 @@ import java.util.Map; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Collections; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service; import com.billpaymentservice.bbps.dto.PaymentStartReturnDto; import com.billpaymentservice.bbps.dto.ResponseModel; 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.PaymentStatusResponse; 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.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.entites.BharatConnectMaster; +import com.billpaymentservice.bbps.entites.PaymentStartRecord; @Service public class PaymentStartServiceImpl implements PaymentStartService { 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 HttpService httpService; @@ -69,7 +73,7 @@ public class PaymentStartServiceImpl implements PaymentStartService { this.bbpsMapperHelper = bbpsMapperHelper; } @Override - public ResponseModel startPayment(PaymentStartUserRequest request) { + public ResponseModel startIntentPayment(PaymentStartUserRequest request) { String requestId = request.getReqId(); String apiName = "StartPayment"; @@ -97,15 +101,17 @@ public class PaymentStartServiceImpl implements PaymentStartService { 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, uatPaymentUrl, + helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrlintent, BBPSConstant.HTTP_POST, forwardJson); // --- CALL EXTERNAL API --- Map responseMap = - httpService.postRequest(uatPaymentUrl, forwardJson, null); + httpService.postRequest(uatPaymentUrlintent, forwardJson, null); 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", ""))); - // 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 if (outcomeCode == 200 || outcomeCode == 205 || outcomeCode == 206) { // success - build apiResponse from wrapper or direct payload @@ -238,7 +232,15 @@ public class PaymentStartServiceImpl implements PaymentStartService { } // 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 PaymentStartReturnDto responseData = new PaymentStartReturnDto(); @@ -322,12 +324,12 @@ public class PaymentStartServiceImpl implements PaymentStartService { "&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8); LOGGER.info("Form Payload: {}", formPayload); OutboundTraceLog outboundLog = - helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrl, + helper.createOutboundLog(txnId, requestId, apiName, uatPaymentUrlweb, BBPSConstant.HTTP_POST, formPayload); // ----------- CALL API (FORM URL-ENCODED) ----------- Map responseMap = - httpService.postFormRequest(uatPaymentUrl, formPayload); + httpService.postFormRequest(uatPaymentUrlweb, formPayload); LOGGER.info("Payment Start Response: {}", responseMap); @@ -350,21 +352,122 @@ public class PaymentStartServiceImpl implements PaymentStartService { // ❗❗ 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); - // Save record with HTML (optional) - // bbpsMapperHelper.savePaymentStartHtmlRecord(request, respBody, txnId, orderNo); + // Return HTML directly in API response + Map result = new HashMap<>(); + result.put("html", respBody); - // Return HTML directly in API response - Map result = new HashMap<>(); - result.put("html", respBody); - - return ResponseModel.builder() - .status("SUCCESS") - .statusCode(200) - .message("HTML Response Received") - .data(result) - .build(); + return ResponseModel.builder() + .status("SUCCESS") + .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); -} \ No newline at end of file + 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 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 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 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); + } + + +} diff --git a/src/main/java/com/billpaymentservice/bbps/util/BillerMapperHelper.java b/src/main/java/com/billpaymentservice/bbps/util/BillerMapperHelper.java index 1e0d756..5612e44 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/BillerMapperHelper.java +++ b/src/main/java/com/billpaymentservice/bbps/util/BillerMapperHelper.java @@ -1081,18 +1081,25 @@ public class BillerMapperHelper { } 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(); record.setOrderNo(orderNo); record.setRequestId(request.getReqId()); record.setClientMid(request.getMid()); record.setClientUserId(request.getClientUserId()); record.setInternalTxnId(txnId); - record.setAmount(apiResponse.getResponseData().getAmount()); - record.setMid(apiResponse.getResponseData().getMid()); - record.setTxnId(apiResponse.getResponseData().getTxnId()); - record.setQrString(apiResponse.getResponseData().getQrString()); - record.setCreatedOn(apiResponse.getResponseData().getCreatedOn()); + if (apiResponse != null && apiResponse.getResponseData() != null) { + record.setAmount(apiResponse.getResponseData().getAmount()); + record.setMid(apiResponse.getResponseData().getMid()); + record.setTxnId(apiResponse.getResponseData().getTxnId()); + 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); } diff --git a/src/main/java/com/billpaymentservice/bbps/util/EncryptionUtil.java b/src/main/java/com/billpaymentservice/bbps/util/EncryptionUtil.java index f99b428..96a47d4 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/EncryptionUtil.java +++ b/src/main/java/com/billpaymentservice/bbps/util/EncryptionUtil.java @@ -1,7 +1,11 @@ package com.billpaymentservice.bbps.util; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Base64; +import java.util.zip.CRC32; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -18,12 +22,9 @@ public class EncryptionUtil { /** * 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). - * 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) { try { - // Ensure key length is 16 bytes (AES-128). If callers provide a longer key this will truncate. byte[] keyBytes = normalizeKey(encryptKey); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); @@ -61,4 +62,56 @@ public class EncryptionUtil { } 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; + } + } +} \ No newline at end of file