Skip to content

Commit

Permalink
Merge pull request #2105 from alphagov/pp_6098_parity_check_additiona…
Browse files Browse the repository at this point in the history
…l_fields

PP-6098 Parity checker: match settlement summary and refundability
  • Loading branch information
kbottla authored Feb 12, 2020
2 parents 0404841 + 7b1b443 commit 3ce59cf
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 34 deletions.
11 changes: 9 additions & 2 deletions src/main/java/uk/gov/pay/connector/charge/dao/ChargeDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,14 @@ public Optional<ChargeEntity> findChargeToExpunge(int minimumAgeOfChargeInDays,
}

public void expungeCharge(Long id) {
ChargeEntity chargeEntity = entityManager.get().find(ChargeEntity.class, id);
entityManager.get().remove(chargeEntity);
entityManager.get()
.createNativeQuery("delete from charge_events where charge_id = ?1")
.setParameter(1, id)
.executeUpdate();

entityManager.get()
.createNativeQuery("delete from charges where id = ?1")
.setParameter(1, id)
.executeUpdate();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package uk.gov.pay.connector.expunge.service;

import com.google.inject.persist.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.gov.pay.connector.app.ConnectorConfiguration;
Expand All @@ -21,7 +22,7 @@ public class ChargeExpungeService {
private final ChargeDao chargeDao;
private final ExpungeConfig expungeConfig;
private final ParityCheckService parityCheckService;

@Inject
public ChargeExpungeService(ChargeDao chargeDao, ConnectorConfiguration connectorConfiguration,
ParityCheckService parityCheckService) {
Expand All @@ -30,6 +31,10 @@ public ChargeExpungeService(ChargeDao chargeDao, ConnectorConfiguration connecto
this.parityCheckService = parityCheckService;
}

private static boolean inTerminalState(ChargeEntity chargeEntity) {
return ChargeStatus.fromString(chargeEntity.getStatus()).isExpungeable();
}

public void expunge(Integer noOfChargesToExpungeQueryParam) {
if (!expungeConfig.isExpungeChargesEnabled()) {
logger.info("Charge expunging feature is disabled. No charges have been expunged");
Expand All @@ -54,12 +59,12 @@ private int getNumberOfChargesToExpunge(Integer noOfChargesToExpungeQueryParam)

private void parityCheckAndExpungeIfMet(ChargeEntity chargeEntity) {
boolean hasChargeBeenParityCheckedBefore = chargeEntity.getParityCheckDate() != null;

if (!inTerminalState(chargeEntity)) {
logger.info("Charge not expunged because it is not in a terminal state {}",
kv(PAYMENT_EXTERNAL_ID, chargeEntity.getExternalId()));
} else if (parityCheckService.parityCheckChargeForExpunger(chargeEntity)) {
chargeDao.expungeCharge(chargeEntity.getId());
expungeCharge(chargeEntity);
logger.info("Charge expunged from connector {}",
kv(PAYMENT_EXTERNAL_ID, chargeEntity.getExternalId()));
} else {
Expand All @@ -73,8 +78,9 @@ private void parityCheckAndExpungeIfMet(ChargeEntity chargeEntity) {
}
}

private static boolean inTerminalState(ChargeEntity chargeEntity) {
return ChargeStatus.fromString(chargeEntity.getStatus()).isExpungeable();
@Transactional
public void expungeCharge(ChargeEntity chargeEntity) {
chargeDao.expungeCharge(chargeEntity.getId());
}

}
55 changes: 51 additions & 4 deletions src/main/java/uk/gov/pay/connector/tasks/ParityCheckService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,45 @@
import org.slf4j.MDC;
import uk.gov.pay.connector.charge.model.AddressEntity;
import uk.gov.pay.connector.charge.model.CardDetailsEntity;
import uk.gov.pay.connector.charge.model.ChargeResponse;
import uk.gov.pay.connector.charge.model.FirstDigitsCardNumber;
import uk.gov.pay.connector.charge.model.LastDigitsCardNumber;
import uk.gov.pay.connector.charge.model.domain.Charge;
import uk.gov.pay.connector.charge.model.domain.ChargeEntity;
import uk.gov.pay.connector.charge.model.domain.ChargeStatus;
import uk.gov.pay.connector.charge.model.domain.ParityCheckStatus;
import uk.gov.pay.connector.charge.service.ChargeService;
import uk.gov.pay.connector.chargeevent.model.domain.ChargeEventEntity;
import uk.gov.pay.connector.common.model.api.ExternalChargeRefundAvailability;
import uk.gov.pay.connector.gateway.util.DefaultExternalRefundAvailabilityCalculator;
import uk.gov.pay.connector.gatewayaccount.model.GatewayAccountEntity;
import uk.gov.pay.connector.paritycheck.Address;
import uk.gov.pay.connector.paritycheck.CardDetails;
import uk.gov.pay.connector.paritycheck.LedgerService;
import uk.gov.pay.connector.paritycheck.LedgerTransaction;
import uk.gov.pay.connector.refund.dao.RefundDao;
import uk.gov.pay.connector.refund.model.domain.RefundEntity;
import uk.gov.pay.connector.util.DateTimeUtils;
import uk.gov.pay.connector.wallets.WalletType;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static net.logstash.logback.argument.StructuredArguments.kv;
import static uk.gov.pay.commons.model.ApiResponseDateTimeFormatter.ISO_INSTANT_MILLISECOND_PRECISION;
import static uk.gov.pay.connector.charge.model.domain.ChargeStatus.CAPTURED;
import static uk.gov.pay.connector.charge.model.domain.ChargeStatus.CAPTURE_SUBMITTED;
import static uk.gov.pay.connector.charge.model.domain.ChargeStatus.CREATED;
import static uk.gov.pay.connector.charge.model.domain.ChargeStatus.PAYMENT_NOTIFICATION_CREATED;
import static uk.gov.pay.connector.charge.model.domain.ParityCheckStatus.DATA_MISMATCH;
import static uk.gov.pay.connector.charge.model.domain.ParityCheckStatus.EXISTS_IN_LEDGER;
import static uk.gov.pay.connector.charge.model.domain.ParityCheckStatus.MISSING_IN_LEDGER;
import static uk.gov.pay.connector.charge.util.CorporateCardSurchargeCalculator.getTotalAmountFor;
import static uk.gov.pay.logging.LoggingKeys.PAYMENT_EXTERNAL_ID;

public class ParityCheckService {
Expand Down Expand Up @@ -130,6 +141,8 @@ private ParityCheckStatus checkParity(ChargeEntity chargeEntity, LedgerTransacti
fieldsMatch = fieldsMatch && matchCardDetails(chargeEntity.getCardDetails(), transaction.getCardDetails());
fieldsMatch = fieldsMatch && matchGatewayAccountFields(chargeEntity.getGatewayAccount(), transaction);
fieldsMatch = fieldsMatch && matchFeatureSpecificFields(chargeEntity, transaction);
fieldsMatch = fieldsMatch && matchCaptureFields(chargeEntity, transaction);
fieldsMatch = fieldsMatch && matchRefundSummary(chargeEntity, transaction);

if (fieldsMatch) {
parityCheckStatus = EXISTS_IN_LEDGER;
Expand All @@ -151,7 +164,8 @@ private boolean matchCommonFields(ChargeEntity chargeEntity, LedgerTransaction t
fieldsMatch = fieldsMatch && isEquals(chargeEntity.getReturnUrl(), transaction.getReturnUrl(), "return_url");
fieldsMatch = fieldsMatch && isEquals(chargeEntity.getGatewayTransactionId(), transaction.getGatewayTransactionId(), "gateway_transaction_id");
fieldsMatch = fieldsMatch && isEquals(
ISO_INSTANT_MILLISECOND_PRECISION.format(chargeEntity.getCreatedDate()),
getChargeEventDate(chargeEntity, List.of(CREATED, PAYMENT_NOTIFICATION_CREATED))
.map(ISO_INSTANT_MILLISECOND_PRECISION::format).orElse(null),
transaction.getCreatedDate(), "created_date");

String chargeExternalStatus = ChargeStatus.fromString(chargeEntity.getStatus()).toExternal().getStatusV2();
Expand Down Expand Up @@ -231,15 +245,48 @@ private boolean matchFeatureSpecificFields(ChargeEntity chargeEntity, LedgerTran
transaction.getCorporateCardSurcharge(), "corporate_surcharge");
fieldsMatch = fieldsMatch && isEquals(chargeEntity.getNetAmount().orElse(null),
transaction.getNetAmount(), "net_amount");

fieldsMatch = fieldsMatch && isEquals(getTotalAmountFor(chargeEntity),
transaction.getTotalAmount(), "total_amount");

fieldsMatch = fieldsMatch &&
isEquals(Optional.ofNullable(chargeEntity.getWalletType())
isEquals(ofNullable(chargeEntity.getWalletType())
.map(WalletType::toString)
.orElse(null), transaction.getWalletType(), "wallet_type");

return fieldsMatch;
}

private boolean matchCaptureFields(ChargeEntity chargeEntity, LedgerTransaction transaction) {
boolean fieldsMatch = isEquals(
getChargeEventDate(chargeEntity, List.of(CAPTURED)).map(DateTimeUtils::toUTCDateString).orElse(null),
ofNullable(transaction.getSettlementSummary()).map(ChargeResponse.SettlementSummary::getCapturedDate).orElse(null),
"captured_date");
fieldsMatch &= isEquals(
getChargeEventDate(chargeEntity, List.of(CAPTURE_SUBMITTED)).map(ISO_INSTANT_MILLISECOND_PRECISION::format).orElse(null),
ofNullable(transaction.getSettlementSummary()).map(ChargeResponse.SettlementSummary::getCaptureSubmitTime).orElse(null),
"capture_submit_time");

return fieldsMatch;
}

private boolean matchRefundSummary(ChargeEntity chargeEntity, LedgerTransaction transaction) {
List<RefundEntity> refundsList = refundDao.findRefundsByChargeExternalId(chargeEntity.getExternalId());

DefaultExternalRefundAvailabilityCalculator defaultExternalRefundAvailabilityCalculator = new DefaultExternalRefundAvailabilityCalculator();
ExternalChargeRefundAvailability refundAvailability = defaultExternalRefundAvailabilityCalculator.calculate(Charge.from(chargeEntity), refundsList);

return isEquals(refundAvailability.getStatus(),
ofNullable(transaction.getRefundSummary()).map(refundSummary -> refundSummary.getStatus()).orElse(null), "refund_summary.status");
}

private Optional<ZonedDateTime> getChargeEventDate(ChargeEntity chargeEntity, List<ChargeStatus> chargeEventStatuses) {
return chargeEntity.getEvents()
.stream()
.filter(chargeEventEntity -> chargeEventStatuses.contains(chargeEventEntity.getStatus()))
.findFirst()
.map(ChargeEventEntity::getUpdated);
}

private boolean isEquals(Object value1, Object value2, String fieldName) {
if (Objects.equals(value1, value2)) {
return true;
Expand Down
16 changes: 10 additions & 6 deletions src/test/java/uk/gov/pay/connector/expunge/service/LedgerStub.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import uk.gov.pay.connector.charge.model.ChargeResponse;
import uk.gov.pay.connector.it.dao.DatabaseFixtures;
import uk.gov.pay.connector.it.util.ChargeUtils;
import uk.gov.pay.connector.model.domain.LedgerTransactionFixture;
import uk.gov.pay.connector.paritycheck.LedgerTransaction;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -33,7 +31,7 @@ public void returnLedgerTransactionWithMismatch(String externalId, DatabaseFixtu
ledgerTransactionFields.put("description", "This is a mismatch");
stubResponse(externalId, ledgerTransactionFields);
}

private void stubResponse(String externalId, Map<String, Object> ledgerTransactionFields) throws JsonProcessingException {
ResponseDefinitionBuilder responseDefBuilder = aResponse()
.withHeader(CONTENT_TYPE, APPLICATION_JSON)
Expand All @@ -47,8 +45,8 @@ private void stubResponse(String externalId, Map<String, Object> ledgerTransacti
)
);
}
private static Map<String, Object> testChargeToLedgerTransactionJson(DatabaseFixtures.TestCharge testCharge) {

private static Map<String, Object> testChargeToLedgerTransactionJson(DatabaseFixtures.TestCharge testCharge) {
var map = new HashMap<String, Object>();
Optional.ofNullable(testCharge.getExternalChargeId()).ifPresent(value -> map.put("id", value));
Optional.of(testCharge.getAmount()).ifPresent(value -> map.put("amount", String.valueOf(value)));
Expand Down Expand Up @@ -79,6 +77,12 @@ private static Map<String, Object> testChargeToLedgerTransactionJson(DatabaseFix
account));
Optional.ofNullable(testCharge.getTestAccount().getPaymentProvider()).ifPresent(account -> map.put("payment_provider",
account));

ChargeResponse.RefundSummary refundSummary = new ChargeResponse.RefundSummary();
refundSummary.setStatus("available");
Optional.ofNullable(testCharge.getTestAccount().getPaymentProvider()).ifPresent(account -> map.put("refund_summary",
refundSummary));

map.put("live", false);
return map;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import static org.hamcrest.core.IsNot.not;
import static uk.gov.pay.commons.model.ApiResponseDateTimeFormatter.ISO_INSTANT_MILLISECOND_PRECISION;
import static uk.gov.pay.commons.model.SupportedLanguage.ENGLISH;
import static uk.gov.pay.connector.charge.model.domain.ChargeStatus.CREATED;
import static uk.gov.pay.connector.junit.DropwizardJUnitRunner.WIREMOCK_PORT;

@RunWith(DropwizardJUnitRunner.class)
Expand Down Expand Up @@ -64,7 +65,6 @@ private void insertTestAccount() {
.insert();
}


@Test
public void shouldExpungeCharge() throws JsonProcessingException {
var chargedId = ThreadLocalRandom.current().nextLong();
Expand All @@ -84,6 +84,7 @@ public void shouldExpungeCharge() throws JsonProcessingException {
.withReturnUrl("https://www.test.test/")
.withChargeStatus(ChargeStatus.CAPTURED)
.insert();
insertChargeEvent(expungeableCharge1);
ledgerStub.returnLedgerTransaction("external_charge_id", expungeableCharge1);
var charge = databaseTestHelper.containsChargeWithExternalId("external_charge_id");
assertThat(charge, is(true));
Expand All @@ -95,7 +96,16 @@ public void shouldExpungeCharge() throws JsonProcessingException {
var postCharge = databaseTestHelper.containsChargeWithExternalId("external_charge_id");
assertThat(postCharge, is(false));
}


private void insertChargeEvent(DatabaseFixtures.TestCharge charge) {
DatabaseFixtures.withDatabaseTestHelper(databaseTestHelper).aTestChargeEvent()
.withChargeStatus(CREATED)
.withDate(charge.getCreatedDate())
.withChargeId(charge.getChargeId())
.withTestCharge(charge)
.insert();
}

@Test
public void shouldUpdateTheParityCheckedDateOfNonCriteriaMatchedCharge() throws JsonProcessingException {
var chargedId = ThreadLocalRandom.current().nextLong();
Expand All @@ -110,6 +120,7 @@ public void shouldUpdateTheParityCheckedDateOfNonCriteriaMatchedCharge() throws
.withAmount(2500)
.withChargeStatus(ChargeStatus.CAPTURED)
.insert();
insertChargeEvent(expungeableCharge1);
ledgerStub.returnLedgerTransactionWithMismatch("external_charge_id", expungeableCharge1);
var charge = databaseTestHelper.containsChargeWithExternalId("external_charge_id");
assertThat(charge, is(true));
Expand Down Expand Up @@ -142,6 +153,7 @@ public void shouldNotExpungeChargeThatIsNotOldEnoughToBeExpunged() throws JsonPr
.withReturnUrl("https://www.test.test/")
.withChargeStatus(ChargeStatus.CAPTURED)
.insert();
insertChargeEvent(expungeableCharge1);
ledgerStub.returnLedgerTransaction("external_charge_id", expungeableCharge1);
var charge = databaseTestHelper.containsChargeWithExternalId("external_charge_id_2");
assertThat(charge, is(true));
Expand Down Expand Up @@ -175,6 +187,7 @@ public void shouldExpungeChargesMeetingCriteriaButNotThoseThatDont() throws Json
.withReturnUrl("https://www.test.test/")
.withChargeStatus(ChargeStatus.CAPTURED)
.insert();
insertChargeEvent(expungeableCharge1);
ledgerStub.returnLedgerTransaction("external_charge_id_10", expungeableCharge1);

var chargedId2 = ThreadLocalRandom.current().nextLong();
Expand All @@ -195,6 +208,7 @@ public void shouldExpungeChargesMeetingCriteriaButNotThoseThatDont() throws Json
.withReturnUrl("https://www.test.test/")
.withChargeStatus(ChargeStatus.CAPTURED)
.insert();
insertChargeEvent(nonExpungeableCharge1);
ledgerStub.returnLedgerTransaction("external_charge_id_11", nonExpungeableCharge1);

var chargedId3 = ThreadLocalRandom.current().nextLong();
Expand All @@ -215,6 +229,7 @@ public void shouldExpungeChargesMeetingCriteriaButNotThoseThatDont() throws Json
.withReturnUrl("https://www.test.test/")
.withChargeStatus(ChargeStatus.CAPTURED)
.insert();
insertChargeEvent(expungeableCharge2);
ledgerStub.returnLedgerTransaction("external_charge_id_12", expungeableCharge2);

var chargedId4 = ThreadLocalRandom.current().nextLong();
Expand All @@ -235,6 +250,7 @@ public void shouldExpungeChargesMeetingCriteriaButNotThoseThatDont() throws Json
.withReturnUrl("https://www.test.test/")
.withChargeStatus(ChargeStatus.CAPTURED)
.insert();
insertChargeEvent(nonExpungeableCharge2);
ledgerStub.returnLedgerTransaction("external_charge_id_13", nonExpungeableCharge2);

given().port(testContext.getPort())
Expand Down
Loading

0 comments on commit 3ce59cf

Please sign in to comment.