Skip to content

Commit

Permalink
[PAGOPA-2389] feat: Add new PAYING status (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
pasqualespica authored Dec 17, 2024
2 parents f6a16bf + 3c15060 commit 63a6482
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import it.gov.pagopa.wispconverter.controller.model.ReceiptTimerRequest;
import it.gov.pagopa.wispconverter.exception.AppErrorCodeMessageEnum;
import it.gov.pagopa.wispconverter.exception.AppException;
import it.gov.pagopa.wispconverter.repository.model.enumz.WorkflowStatus;
import it.gov.pagopa.wispconverter.service.ReceiptTimerService;
import it.gov.pagopa.wispconverter.service.*;
import it.gov.pagopa.wispconverter.service.model.ReceiptDto;
import it.gov.pagopa.wispconverter.service.model.session.SessionDataDTO;
import it.gov.pagopa.wispconverter.util.EndpointRETrace;
import it.gov.pagopa.wispconverter.util.ErrorUtil;
import lombok.RequiredArgsConstructor;
Expand All @@ -34,6 +38,10 @@ public class ReceiptTimerController {

private final ReceiptTimerService receiptTimerService;

private final RtReceiptCosmosService rtReceiptCosmosService;

private final RPTExtractorService rptExtractorService;

private final ErrorUtil errorUtil;

@Value("${wisp-converter.receipttimer-delta-activate.expirationtime.ms}")
Expand Down Expand Up @@ -67,6 +75,23 @@ public void createTimer(@RequestBody ReceiptTimerRequest request) {
@EndpointRETrace(status = WorkflowStatus.PAYMENT_TOKEN_TIMER_DELETION_PROCESSED, businessProcess = BP_TIMER_DELETE, reEnabled = true)
public void deleteTimer(@RequestParam() String paymentTokens) {
List<String> tokens = Arrays.asList(paymentTokens.split(","));
receiptTimerService.cancelScheduledMessage(tokens);
try {
// set receipts-rt status to PAYING stands for waiting SendPaymentOutcome and then paSendRTV2 (after the latter, the state will change to SENDING -> SENT)
// Get sessionData for the first ReceiptDTO because by design session-id is equal for all paymentTokens in input
ReceiptDto receiptDto = receiptTimerService.peek(tokens.get(0));
if(receiptDto != null) {
String sessionId = receiptDto.getSessionId();
SessionDataDTO sessionDataDTO = rptExtractorService.getSessionDataFromSessionId(sessionId);
// Update receipts-rt status to PAYING
sessionDataDTO.getAllRPTs().forEach(rtReceiptCosmosService::updateStatusToPaying);
}
} catch (AppException appException) {
throw appException;
} catch (Exception e) {
throw new AppException(AppErrorCodeMessageEnum.RECEIPT_RT_STATUS_TO_PAYING_FAILURE, e);
} finally {
// cancel scheduled message if PAYING status transition goes in exception
receiptTimerService.cancelScheduledMessage(tokens);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,4 @@ public String redirect(@Parameter(description = "", example = "identificativoInt
return "error";
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public enum AppErrorCodeMessageEnum {
RECEIPT_KO_NOT_GENERATED_BUT_MAYBE_RESCHEDULED(1409, "KO Receipt not generated", "Error while generating KO receipt. It is not possible to generate the receipt and it could be scheduled for a next send.", HttpStatus.UNPROCESSABLE_ENTITY, "An error occurred while generating a negative RT (aka a KO receipt). The receipt could be sent lately to creditor institution but for better understanding the cause, please use the Technical Support's APIs."),
RECEIPT_OK_NOT_GENERATED_BUT_MAYBE_RESCHEDULED(1410, "OK Receipt not generated", "Error while generating OK receipt. It is not possible to generate the receipt and it could be scheduled for a next send.", HttpStatus.UNPROCESSABLE_ENTITY, "An error occurred while generating a positive RT (aka a OK receipt). The receipt could be sent lately to creditor institution but for better understanding the cause, please use the Technical Support's APIs."),
RECEIPT_GENERATION_ERROR_DEAD_LETTER(1411, "Receipt generation not completed", "Error while generating receipt. The creditor institution sent an error response related to the sent RT: [Outcome: {0}, Fault code: {1}, Fault string: {2}, Fault description: {3}].", HttpStatus.UNPROCESSABLE_ENTITY, "An error occurred while generating an RT (aka a receipt). Specifically, the creditor institution response status has not been recognized, for this reason the RT has been placed in the dead letter container."),
RECEIPT_RT_STATUS_TO_PAYING_FAILURE(1412, "Receipt status update error", "Error while trying to update receipt status to PAYING at the time of DELETE receipt/time call.", HttpStatus.INTERNAL_SERVER_ERROR, "An error occurred while trying to update receipt status to PAYING at the time of DELETE receipt/timer i.e. ClosePayment inbound. This error may cause side effects such as sending ko if paSendRTV2 (ie.SendPaymentOutcome sending) is received with a n-hour delay."),
// --- DB and storage interaction errors ---
PERSISTENCE_SAVING_RE_ERROR(2000, "Impossible to save event", "Error while trying to store an event in Registro Eventi. Impossible to store event: {0}.", HttpStatus.INTERNAL_SERVER_ERROR, "An error occurred wile trying to store a new event in the Registro Eventi storage. The error is somewhat related to a persistence problem of the used storage and in the majority of the cases is temporary (maybe a 429 HTTP code). This error currently does not blocks the entire flow but, for better understanding the cause, please execute a search in the log provider (Application Insights, Kibana, etc)."),
PERSISTENCE_RPT_NOT_FOUND(2001, "RPT not found", "Error while retrieving RPT. RPT with sessionId [{0}] not found.", HttpStatus.NOT_FOUND, "An error occurred while trying to retrieve the RPT content saved in storage by WISP SOAP Converter. This can be related either with the use of a wrong sessionId or a missed persistence from WISP SOAP Converter, so it is better to analyze the entire flow using Technical Support's APIs. This block totally the conversion of the RPTs in GPD's payment positions, so the whole process is discarded."),
Expand All @@ -66,6 +67,7 @@ public enum AppErrorCodeMessageEnum {
CLIENT_CARTSESSION_CACHING(3006, "Cart caching client error", "Error while communicating with cart caching API. {0}", HttpStatus.INTERNAL_SERVER_ERROR, "An error occurred while communicating with an internal service endpoint dedicated to storing internal cache for handle unique session on cart. It can be related to any client problem, so the best way to handle this is to use the Technical Support's APIs in order to find the cause."),
UNKNOWN(0, "Unknown", "Unknown error", HttpStatus.INTERNAL_SERVER_ERROR, null);


private final Integer code;
private final String title;
private final String detail;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

public enum ReceiptStatusEnum {
REDIRECT,
// PAYING status cover the time between DELETE receipt/timer and (POST receipt/ok or receipt/ko) calls
PAYING,
SENDING,
SCHEDULED,
SENT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import it.gov.digitpa.schemas._2011.pagamenti.CtRichiestaPagamentoTelematico;
import it.gov.pagopa.wispconverter.exception.AppErrorCodeMessageEnum;
import it.gov.pagopa.wispconverter.exception.AppException;
import it.gov.pagopa.wispconverter.repository.model.RPTRequestEntity;
import it.gov.pagopa.wispconverter.service.mapper.RPTMapper;
import it.gov.pagopa.wispconverter.service.model.PaymentRequestDomainDTO;
import it.gov.pagopa.wispconverter.service.model.paymentrequest.PaymentRequestDTO;
Expand Down Expand Up @@ -35,7 +36,7 @@ public class RPTExtractorService {

private final ConfigCacheService cacheService;

private final ReService reService;
private final RptCosmosService rptCosmosService;

private final JaxbElementUtil jaxbElementUtil;

Expand Down Expand Up @@ -292,18 +293,11 @@ private PaymentRequestDTO extractRPT(byte[] rptBytes) {
return paymentRequest;
}

/*
public void sendEventForExtractedRPTs(Collection<RPTContentDTO> rpts) {
public SessionDataDTO getSessionDataFromSessionId(String sessionId) {
// try to retrieve the RPT previously persisted in storage from the sessionId
RPTRequestEntity rptRequestEntity = rptCosmosService.getRPTRequestEntity(sessionId);

// creating event to be persisted for RE
if (Boolean.TRUE.equals(isTracingOnREEnabled)) {
for (RPTContentDTO rpt : rpts) {
MDC.put(Constants.MDC_DOMAIN_ID, rpt.getRpt().getDomain().getDomainId());
MDC.put(Constants.MDC_IUV, rpt.getIuv());
MDC.put(Constants.MDC_CCP, rpt.getCcp());
reService.sendEvent(WorkflowStatus.RPT_EXTRACTED);
}
}
// use the retrieved RPT for generate session data information on which the next execution will operate
return this.extractSessionData(rptRequestEntity.getPrimitive(), rptRequestEntity.getPayload());
}
*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import it.gov.pagopa.wispconverter.exception.AppErrorCodeMessageEnum;
import it.gov.pagopa.wispconverter.exception.AppException;
import it.gov.pagopa.wispconverter.repository.ReceiptDeadLetterRepository;
import it.gov.pagopa.wispconverter.repository.model.RPTRequestEntity;
import it.gov.pagopa.wispconverter.repository.model.RTRequestEntity;
import it.gov.pagopa.wispconverter.repository.model.ReceiptDeadLetterEntity;
import it.gov.pagopa.wispconverter.repository.model.enumz.*;
Expand Down Expand Up @@ -68,8 +67,6 @@ public class ReceiptService {

private final ConfigCacheService configCacheService;

private final RptCosmosService rptCosmosService;

private final RtReceiptCosmosService rtReceiptCosmosService;

private final RtRetryComosService rtRetryComosService;
Expand Down Expand Up @@ -202,7 +199,7 @@ private void handleSingleReceiptForKOPaaInviaRt(ReceiptDto receipt, SessionIdDto
String iuv = retrieveIuvFromCache(receipt, noticeNumber);

// use the session-id for generate session data information on which the next execution will operate
SessionDataDTO sessionData = getSessionDataFromSessionId(receipt.getSessionId());
SessionDataDTO sessionData = rptExtractorService.getSessionDataFromSessionId(receipt.getSessionId());
CommonFieldsDTO commonFields = sessionData.getCommonFields();

/*
Expand Down Expand Up @@ -291,7 +288,7 @@ public void sendOkPaaInviaRtToCreditorInstitution(String payload) {
CachedKeysMapping cachedMapping = decouplerService.getCachedMappingFromNavToIuv(paSendRTV2Request.getIdPA(), noticeNumber);

// use session-id for generate session data information on which the next execution will operate
SessionDataDTO sessionData = getSessionDataFromSessionId(paymentNote);
SessionDataDTO sessionData = rptExtractorService.getSessionDataFromSessionId(paymentNote);
CommonFieldsDTO commonFields = sessionData.getCommonFields();

// retrieve station from cache and extract receipt from request
Expand Down Expand Up @@ -392,14 +389,6 @@ public ReceiptContentDTO generateOkRtFromSessionData(
.build();
}

public SessionDataDTO getSessionDataFromSessionId(String sessionId) {
// try to retrieve the RPT previously persisted in storage from the sessionId
RPTRequestEntity rptRequestEntity = rptCosmosService.getRPTRequestEntity(sessionId);

// use the retrieved RPT for generate session data information on which the next execution will operate
return this.rptExtractorService.extractSessionData(rptRequestEntity.getPrimitive(), rptRequestEntity.getPayload());
}

private void sendReceiptToCreditorInstitution(
SessionDataDTO sessionData,
RPTContentDTO rpt,
Expand Down Expand Up @@ -755,7 +744,7 @@ public void sendRTKoFromSessionId(String sessionId) {
sessionIdDto.setSessionId(sessionId);
this.deleteHangTimerCacheKey(sessionIdDto);

SessionDataDTO sessionDataDTO = getSessionDataFromSessionId(sessionId);
SessionDataDTO sessionDataDTO = rptExtractorService.getSessionDataFromSessionId(sessionId);
gov.telematici.pagamenti.ws.papernodo.ObjectFactory objectFactory = new gov.telematici.pagamenti.ws.papernodo.ObjectFactory();

// retrieve configuration data from cache
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package it.gov.pagopa.wispconverter.service;

import com.azure.messaging.servicebus.ServiceBusClientBuilder;
import com.azure.messaging.servicebus.ServiceBusMessage;
import com.azure.messaging.servicebus.ServiceBusSenderClient;
import com.azure.messaging.servicebus.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import it.gov.pagopa.wispconverter.controller.model.ReceiptTimerRequest;
import it.gov.pagopa.wispconverter.repository.CacheRepository;
Expand All @@ -18,6 +17,7 @@
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
Expand All @@ -30,6 +30,7 @@ public class ReceiptTimerService {

public static final String CACHING_KEY_TEMPLATE = "wisp_timer_%s";
public static final String PAYMENT_TOKEN_CACHING_KEY_TEMPLATE = "2_wisp_%s";
private final ObjectMapper mapper = new ObjectMapper();
private final CacheRepository cacheRepository;
private final ReService reService;
@Value("${azure.sb.wisp-payment-timeout-queue.connectionString}")
Expand All @@ -39,17 +40,22 @@ public class ReceiptTimerService {
@Value("${disable-service-bus-sender}")
private boolean disableServiceBusSender;
private ServiceBusSenderClient serviceBusSenderClient;
private ServiceBusReceiverClient serviceBusReceiverClient;
@Autowired
private ECommerceHangTimerService eCommerceHangTimerService;

@PostConstruct
public void post() {
serviceBusSenderClient =
new ServiceBusClientBuilder()
.connectionString(connectionString)
ServiceBusClientBuilder builder = new ServiceBusClientBuilder();
serviceBusSenderClient = builder.connectionString(connectionString)
.sender()
.queueName(queueName)
.buildClient();

serviceBusReceiverClient = builder.connectionString(connectionString)
.receiver()
.queueName(queueName)
.buildClient();
}

public void sendMessage(ReceiptTimerRequest message) {
Expand Down Expand Up @@ -102,6 +108,21 @@ public void sendMessage(ReceiptTimerRequest message) {
}
}

public ReceiptDto peek(String paymentToken) {
// read sequence number from redis cache
String sequenceNumberKey = String.format(CACHING_KEY_TEMPLATE, paymentToken);
String sequenceNumberString = cacheRepository.read(sequenceNumberKey, String.class);
// read message without changing the service bus state
ServiceBusReceivedMessage message = serviceBusReceiverClient.peekMessage(Long.parseLong(sequenceNumberString));
log.debug("Get message. Session: {}, Sequence #: {}. Contents: {}", message.getMessageId(), message.getSequenceNumber(), message.getBody());
try {
return mapper.readValue(message.getBody().toStream(), ReceiptDto.class);
} catch (Exception e) {
log.error("Error when read ReceiptDto value from message: '{}'. Body: '{}'", message.getMessageId(), message.getBody());
return null;
}
}

public void cancelScheduledMessage(List<String> paymentTokens) {
if (!disableServiceBusSender) {
paymentTokens.forEach(this::cancelScheduledMessage);
Expand Down Expand Up @@ -130,7 +151,7 @@ public void callCancelScheduledMessage(String sequenceNumberString) {
try {
serviceBusSenderClient.cancelScheduledMessage(sequenceNumber);
} catch (Exception exception) {
log.debug(String.format("Scheduled message with sequence number [%s] not deleted. Cause: %s", sequenceNumberString, exception.getMessage()));
log.debug("Scheduled message with sequence number [{}] not deleted. Cause: {}", sequenceNumberString, exception.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,44 @@ public boolean updateReceiptStatus(String ci, String iuv, String ccp, ReceiptSta
}
}

@Transactional
public boolean updateReceiptStatus(RTEntity rtEntity, ReceiptStatusEnum status) {
try {
rtEntity.setReceiptStatus(status);
rtRepository.save(rtEntity);

return true;
} catch (CosmosException e) {
log.error("An exception occurred while saveRTEntity: " + e.getMessage());
return false;
}
}

public boolean updateStatusToPaying(RPTContentDTO rptContentDTO) {
String fiscalCode = rptContentDTO.getRpt().getDomain().getDomainId();
Optional<RTEntity> rtEntityOpt = this.findById(fiscalCode, rptContentDTO.getIuv(), rptContentDTO.getCcp());
if(rtEntityOpt.isPresent()) {
RTEntity rtEntity = rtEntityOpt.get();
// change status only if is REDIRECT, if it is equals or next to PAYING (ie PAYING, SENDING, SENT)
// must not go back or overwritten with the same value
if(rtEntity.getReceiptStatus().equals(ReceiptStatusEnum.REDIRECT)) {
this.updateReceiptStatus(rtEntity, ReceiptStatusEnum.PAYING);
log.debug("Receipt-rt with id = {} has been updated with status PAYING", rtEntity.getId());
return true;
}
log.warn("Attempt to update receipt-rt with id = {} has been failed because the current status is {}",
rtEntity.getId(), rtEntity.getReceiptStatus());
}

return false;
}

public Optional<RTEntity> findById(String domainId, String iuv, String ccp) {
String id = String.format("%s_%s_%s", domainId, iuv, ccp);
// Remove illegal characters ['/', '\', '#'] because cannot be used in Resource ID
return rtRepository.findById(id, new PartitionKey(id));
}

public boolean receiptRtExist(String domainId, String iuv, String ccp) {
String id = getId(domainId, iuv, ccp);
return rtRepository.findById(id, new PartitionKey(id)).isPresent();
Expand Down

0 comments on commit 63a6482

Please sign in to comment.