From f4ae7242a3fd55ec75132c8ee92e0b79b2c90392 Mon Sep 17 00:00:00 2001 From: Sandor Arpa Date: Thu, 19 Dec 2024 14:04:39 +0000 Subject: [PATCH] PP-13334 API returns exemption object API should return exemption object in payment responses when appropriate. --- openapi/publicapi_spec.json | 40 ++++++++ .../uk/gov/pay/api/model/CardPayment.java | 10 +- .../java/uk/gov/pay/api/model/Charge.java | 18 +++- .../gov/pay/api/model/ChargeFromResponse.java | 6 ++ .../java/uk/gov/pay/api/model/Exemption.java | 44 +++++++++ .../gov/pay/api/model/ExemptionOutcome.java | 28 ++++++ .../pay/api/model/TransactionResponse.java | 7 ++ .../api/model/links/PaymentWithAllLinks.java | 25 +++-- .../search/card/PaymentForSearchResult.java | 9 +- .../gov/pay/api/utils/ExemptionEvaluator.java | 20 ++++ .../gov/pay/api/it/PaymentsResourceGetIT.java | 90 +++++++++++++++++ .../pay/api/it/PaymentsResourceSearchIT.java | 24 +++++ .../api/it/fixtures/PaymentResultBuilder.java | 4 + .../fixtures/PaymentSearchResultBuilder.java | 6 ++ .../fixtures/PaymentSingleResultBuilder.java | 6 ++ .../GetPaymentServiceLedgerPactTest.java | 14 +++ .../pay/api/utils/ExemptionEvaluatorTest.java | 41 ++++++++ .../mocks/ChargeResponseFromConnector.java | 16 +++- .../api/utils/mocks/MockHelperFunctions.java | 1 + .../mocks/TransactionFromLedgerFixture.java | 13 +++ ...r-get-payment-with-honoured-exemption.json | 96 +++++++++++++++++++ 21 files changed, 502 insertions(+), 16 deletions(-) create mode 100644 src/main/java/uk/gov/pay/api/model/Exemption.java create mode 100644 src/main/java/uk/gov/pay/api/model/ExemptionOutcome.java create mode 100644 src/main/java/uk/gov/pay/api/utils/ExemptionEvaluator.java create mode 100644 src/test/java/uk/gov/pay/api/utils/ExemptionEvaluatorTest.java create mode 100644 src/test/resources/pacts/publicapi-ledger-get-payment-with-honoured-exemption.json diff --git a/openapi/publicapi_spec.json b/openapi/publicapi_spec.json index f72238e38..9bb1f62b8 100644 --- a/openapi/publicapi_spec.json +++ b/openapi/publicapi_spec.json @@ -1890,6 +1890,27 @@ } } }, + "Exemption" : { + "type" : "object", + "description" : "A structure representing that 3DS exemption was requested and the outcome of the exemption, if applicable.", + "properties" : { + "outcome" : { + "$ref" : "#/components/schemas/Outcome" + }, + "requested" : { + "type" : "boolean", + "description" : "Indicates whether an exemption was requested for the given payment.", + "example" : true, + "readOnly" : true + }, + "type" : { + "type" : "string", + "description" : "Indicates the type of exemption. Only present for corporate exemption", + "example" : "corporate", + "readOnly" : true + } + } + }, "ExternalMetadata" : { "type" : "object", "example" : "{\"property1\": \"value1\", \"property2\": \"value2\"}\"", @@ -1920,6 +1941,19 @@ } } }, + "Outcome" : { + "type" : "object", + "description" : "A structure representing the outcome of a 3DS exemption, if known.", + "properties" : { + "result" : { + "type" : "string", + "description" : "The outcome of the requested exemption", + "example" : "honoured", + "readOnly" : true + } + }, + "readOnly" : true + }, "PaymentDetailForSearch" : { "type" : "object", "description" : "Contains payments matching your search criteria.", @@ -1978,6 +2012,9 @@ "type" : "string", "example" : "The paying user’s email address." }, + "exemption" : { + "$ref" : "#/components/schemas/Exemption" + }, "fee" : { "type" : "integer", "format" : "int64", @@ -2357,6 +2394,9 @@ "type" : "string", "example" : "The paying user’s email address." }, + "exemption" : { + "$ref" : "#/components/schemas/Exemption" + }, "fee" : { "type" : "integer", "format" : "int64", diff --git a/src/main/java/uk/gov/pay/api/model/CardPayment.java b/src/main/java/uk/gov/pay/api/model/CardPayment.java index 194b49355..05575f506 100644 --- a/src/main/java/uk/gov/pay/api/model/CardPayment.java +++ b/src/main/java/uk/gov/pay/api/model/CardPayment.java @@ -96,12 +96,14 @@ public class CardPayment { @JsonProperty("authorisation_mode") private AuthorisationMode authorisationMode; + private Exemption exemption; + public CardPayment(String chargeId, long amount, PaymentState state, String returnUrl, String description, String reference, String email, String paymentProvider, String createdDate, RefundSummary refundSummary, PaymentSettlementSummary settlementSummary, CardDetails cardDetails, SupportedLanguage language, boolean delayedCapture, boolean moto, Long corporateCardSurcharge, Long totalAmount, String providerId, ExternalMetadata metadata, Long fee, Long netAmount, AuthorisationSummary authorisationSummary, String agreementId, - AuthorisationMode authorisationMode) { + AuthorisationMode authorisationMode, Exemption exemption) { this.paymentId = chargeId; this.amount = amount; this.description = description; @@ -127,6 +129,7 @@ public CardPayment(String chargeId, long amount, PaymentState state, String retu this.authorisationSummary = authorisationSummary; this.agreementId = agreementId; this.authorisationMode = authorisationMode; + this.exemption = exemption; } /** @@ -286,6 +289,11 @@ public String getPaymentProvider() { return paymentProvider; } + @Schema(description = "Object containing information about 3DS exemption requests") + public Exemption getExemption() { + return exemption; + } + @Override public String toString() { // Don't include: diff --git a/src/main/java/uk/gov/pay/api/model/Charge.java b/src/main/java/uk/gov/pay/api/model/Charge.java index 6341f48e5..486c2513d 100644 --- a/src/main/java/uk/gov/pay/api/model/Charge.java +++ b/src/main/java/uk/gov/pay/api/model/Charge.java @@ -1,6 +1,7 @@ package uk.gov.pay.api.model; import uk.gov.pay.api.utils.AuthorisationSummaryHelper; +import uk.gov.pay.api.utils.ExemptionEvaluator; import uk.gov.service.payments.commons.model.AuthorisationMode; import uk.gov.service.payments.commons.model.SupportedLanguage; import uk.gov.service.payments.commons.model.charge.ExternalMetadata; @@ -60,13 +61,16 @@ public class Charge { private AuthorisationMode authorisationMode; + private Exemption exemption; + public Charge(String chargeId, Long amount, PaymentState state, String returnUrl, String description, String reference, String email, String paymentProvider, String createdDate, SupportedLanguage language, boolean delayedCapture, boolean moto, RefundSummary refundSummary, PaymentSettlementSummary settlementSummary, CardDetails cardDetails, List links, Long corporateCardSurcharge, Long totalAmount, String gatewayTransactionId, ExternalMetadata metadata, Long fee, Long netAmount, - AuthorisationSummary authorisationSummary, String agreementId, AuthorisationMode authorisationMode) { + AuthorisationSummary authorisationSummary, String agreementId, AuthorisationMode authorisationMode, + Exemption exemption) { this.chargeId = chargeId; this.amount = amount; this.state = state; @@ -92,6 +96,7 @@ public Charge(String chargeId, Long amount, PaymentState state, String returnUrl this.authorisationSummary = authorisationSummary; this.agreementId = agreementId; this.authorisationMode = authorisationMode; + this.exemption = exemption; } public static Charge from(ChargeFromResponse chargeFromResponse) { @@ -122,7 +127,8 @@ public static Charge from(ChargeFromResponse chargeFromResponse) { chargeFromResponse.getNetAmount(), AuthorisationSummaryHelper.includeAuthorisationSummaryWhen3dsRequired(chargeFromResponse.getAuthorisationSummary()), chargeFromResponse.getAgreementId(), - chargeFromResponse.getAuthorisationMode() + chargeFromResponse.getAuthorisationMode(), + ExemptionEvaluator.evaluateExemption(chargeFromResponse.getExemption()) ); } @@ -154,7 +160,9 @@ public static Charge from(TransactionResponse transactionResponse) { transactionResponse.getNetAmount(), AuthorisationSummaryHelper.includeAuthorisationSummaryWhen3dsRequired(transactionResponse.getAuthorisationSummary()), null, - transactionResponse.getAuthorisationMode()); + transactionResponse.getAuthorisationMode(), + ExemptionEvaluator.evaluateExemption(transactionResponse.getExemption()) + ); } public Optional getMetadata() { @@ -260,4 +268,8 @@ public String getAgreementId() { public AuthorisationMode getAuthorisationMode() { return authorisationMode; } + + public Exemption getExemption() { + return exemption; + } } diff --git a/src/main/java/uk/gov/pay/api/model/ChargeFromResponse.java b/src/main/java/uk/gov/pay/api/model/ChargeFromResponse.java index baa959f39..2b6ef0dd7 100644 --- a/src/main/java/uk/gov/pay/api/model/ChargeFromResponse.java +++ b/src/main/java/uk/gov/pay/api/model/ChargeFromResponse.java @@ -94,6 +94,8 @@ public class ChargeFromResponse { @JsonDeserialize(using = WalletDeserializer.class) private Wallet walletType; + private Exemption exemption; + public Optional getMetadata() { return Optional.ofNullable(metadata); } @@ -232,4 +234,8 @@ public AuthorisationMode getAuthorisationMode() { public Optional getWalletType() { return Optional.ofNullable(walletType); } + + public Exemption getExemption() { + return exemption; + } } diff --git a/src/main/java/uk/gov/pay/api/model/Exemption.java b/src/main/java/uk/gov/pay/api/model/Exemption.java new file mode 100644 index 000000000..6310da998 --- /dev/null +++ b/src/main/java/uk/gov/pay/api/model/Exemption.java @@ -0,0 +1,44 @@ +package uk.gov.pay.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@Schema(name = "Exemption", description = "A structure representing that 3DS exemption was requested and the outcome of the exemption, if applicable.") +public class Exemption { + @JsonProperty("requested") + private boolean requested; + @JsonProperty("type") + private String type; + @JsonProperty("outcome") + private ExemptionOutcome outcome; + + public Exemption() { + } + + public Exemption(Boolean requested, String type, ExemptionOutcome outcome) { + this.requested = requested; + this.type = type; + this.outcome = outcome; + } + + @Schema(description = "Indicates whether an exemption was requested for the given payment.", example = "true", accessMode = READ_ONLY) + public boolean getRequested() { + return requested; + } + + @Schema(description = "Indicates the type of exemption. Only present for corporate exemption", example = "corporate", accessMode = READ_ONLY) + public String getType() { + return type; + } + + @Schema(description = "The outcome of the requested exemption", accessMode = READ_ONLY) + public ExemptionOutcome getOutcome() { + return outcome; + } +} diff --git a/src/main/java/uk/gov/pay/api/model/ExemptionOutcome.java b/src/main/java/uk/gov/pay/api/model/ExemptionOutcome.java new file mode 100644 index 000000000..01fef5726 --- /dev/null +++ b/src/main/java/uk/gov/pay/api/model/ExemptionOutcome.java @@ -0,0 +1,28 @@ +package uk.gov.pay.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@Schema(name = "Outcome", description = "A structure representing the outcome of a 3DS exemption, if known.") +public class ExemptionOutcome { + @JsonProperty("result") + private String result; + + public ExemptionOutcome() { + + } + public ExemptionOutcome(String result) { + this.result = result; + } + + @Schema(description = "The outcome of the requested exemption", example = "honoured", accessMode = READ_ONLY) + public String getResult() { + return result; + } +} diff --git a/src/main/java/uk/gov/pay/api/model/TransactionResponse.java b/src/main/java/uk/gov/pay/api/model/TransactionResponse.java index db36fa48d..01d27de7d 100644 --- a/src/main/java/uk/gov/pay/api/model/TransactionResponse.java +++ b/src/main/java/uk/gov/pay/api/model/TransactionResponse.java @@ -89,6 +89,9 @@ public class TransactionResponse { @JsonDeserialize(using = WalletDeserializer.class) private Wallet walletType; + @JsonProperty("exemption") + private Exemption exemption; + public Optional getMetadata() { return Optional.ofNullable(metadata); } @@ -199,4 +202,8 @@ public AuthorisationMode getAuthorisationMode() { public Optional getWalletType() { return Optional.ofNullable(walletType); } + + public Exemption getExemption() { + return exemption; + } } diff --git a/src/main/java/uk/gov/pay/api/model/links/PaymentWithAllLinks.java b/src/main/java/uk/gov/pay/api/model/links/PaymentWithAllLinks.java index 98f39ae0b..4cab28b9a 100644 --- a/src/main/java/uk/gov/pay/api/model/links/PaymentWithAllLinks.java +++ b/src/main/java/uk/gov/pay/api/model/links/PaymentWithAllLinks.java @@ -5,6 +5,7 @@ import uk.gov.pay.api.model.CardDetails; import uk.gov.pay.api.model.CardPayment; import uk.gov.pay.api.model.Charge; +import uk.gov.pay.api.model.Exemption; import uk.gov.pay.api.model.PaymentConnectorResponseLink; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.pay.api.model.PaymentState; @@ -26,14 +27,15 @@ public PaymentLinks getLinks() { } private PaymentWithAllLinks(String chargeId, long amount, PaymentState state, String returnUrl, String description, - String reference, String email, String paymentProvider, String createdDate, SupportedLanguage language, - boolean delayedCapture, boolean moto, RefundSummary refundSummary, PaymentSettlementSummary settlementSummary, CardDetails cardDetails, - List paymentConnectorResponseLinks, URI selfLink, URI paymentEventsUri, URI paymentCancelUri, - URI paymentRefundsUri, URI paymentCaptureUri, URI paymentAuthorisationUri, Long corporateCardSurcharge, Long totalAmount, String providerId, ExternalMetadata metadata, - Long fee, Long netAmount, AuthorisationSummary authorisationSummary, String agreementId, AuthorisationMode authorisationMode) { + String reference, String email, String paymentProvider, String createdDate, SupportedLanguage language, + boolean delayedCapture, boolean moto, RefundSummary refundSummary, PaymentSettlementSummary settlementSummary, CardDetails cardDetails, + List paymentConnectorResponseLinks, URI selfLink, URI paymentEventsUri, URI paymentCancelUri, + URI paymentRefundsUri, URI paymentCaptureUri, URI paymentAuthorisationUri, Long corporateCardSurcharge, Long totalAmount, String providerId, ExternalMetadata metadata, + Long fee, Long netAmount, AuthorisationSummary authorisationSummary, String agreementId, AuthorisationMode authorisationMode, + Exemption exemption) { super(chargeId, amount, state, returnUrl, description, reference, email, paymentProvider, createdDate, refundSummary, settlementSummary, cardDetails, language, delayedCapture, moto, corporateCardSurcharge, totalAmount, - providerId, metadata, fee, netAmount, authorisationSummary, agreementId, authorisationMode); + providerId, metadata, fee, netAmount, authorisationSummary, agreementId, authorisationMode, exemption); this.links.addSelf(selfLink.toString()); this.links.addKnownLinksValueOf(paymentConnectorResponseLinks, paymentAuthorisationUri); this.links.addEvents(paymentEventsUri.toString()); @@ -87,6 +89,7 @@ public static PaymentWithAllLinks valueOf(Charge paymentConnector, .withAuthorisationSummary(paymentConnector.getAuthorisationSummary()) .withAgreementId(paymentConnector.getAgreementId()) .withAuthorisationMode(paymentConnector.getAuthorisationMode()) + .withExemption(paymentConnector.getExemption()) .build(); } @@ -134,6 +137,7 @@ public static class PaymentWithAllLinksBuilder { private AuthorisationSummary authorisationSummary; private String agreementId; private AuthorisationMode authorisationMode; + private Exemption exemption; public PaymentWithAllLinksBuilder withChargeId(String chargeId) { this.chargeId = chargeId; @@ -290,12 +294,17 @@ public PaymentWithAllLinksBuilder withAuthorisationMode(AuthorisationMode author return this; } + public PaymentWithAllLinksBuilder withExemption(Exemption exemption) { + this.exemption = exemption; + return this; + } + public PaymentWithAllLinks build() { - return new PaymentWithAllLinks(chargeId, amount, state, returnUrl, description, reference, email, + return new PaymentWithAllLinks(chargeId, amount, state, returnUrl, description, reference, email, paymentProvider, createdDate, language, delayedCapture, moto, refundSummary, settlementSummary, cardDetails, paymentConnectorResponseLinks, selfLink, paymentEventsUri, paymentCancelUri, paymentRefundsUri, paymentCaptureUri, paymentAuthorisationUri, corporateCardSurcharge, totalAmount, - providerId, metadata, fee, netAmount, authorisationSummary, agreementId, authorisationMode); + providerId, metadata, fee, netAmount, authorisationSummary, agreementId, authorisationMode, exemption); } } } diff --git a/src/main/java/uk/gov/pay/api/model/search/card/PaymentForSearchResult.java b/src/main/java/uk/gov/pay/api/model/search/card/PaymentForSearchResult.java index f947cfecf..d26b97628 100644 --- a/src/main/java/uk/gov/pay/api/model/search/card/PaymentForSearchResult.java +++ b/src/main/java/uk/gov/pay/api/model/search/card/PaymentForSearchResult.java @@ -5,6 +5,7 @@ import uk.gov.pay.api.model.AuthorisationSummary; import uk.gov.pay.api.model.CardDetails; import uk.gov.pay.api.model.CardPayment; +import uk.gov.pay.api.model.Exemption; import uk.gov.pay.api.model.PaymentConnectorResponseLink; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.pay.api.model.PaymentState; @@ -30,11 +31,12 @@ public PaymentForSearchResult(String chargeId, long amount, PaymentState state, boolean delayedCapture, boolean moto, RefundSummary refundSummary, PaymentSettlementSummary settlementSummary, CardDetails cardDetails, List links, URI selfLink, URI paymentEventsLink, URI paymentCancelLink, URI paymentRefundsLink, URI paymentCaptureUri, Long corporateCardSurcharge, Long totalAmount, String providerId, ExternalMetadata externalMetadata, - Long fee, Long netAmount, AuthorisationSummary authorisationSummary, AuthorisationMode authorisationMode) { + Long fee, Long netAmount, AuthorisationSummary authorisationSummary, AuthorisationMode authorisationMode, + Exemption exemption) { super(chargeId, amount, state, returnUrl, description, reference, email, paymentProvider, createdDate, refundSummary, settlementSummary, cardDetails, language, delayedCapture, moto, corporateCardSurcharge, totalAmount, providerId, externalMetadata, - fee, netAmount, authorisationSummary, null, authorisationMode); + fee, netAmount, authorisationSummary, null, authorisationMode, exemption); this.links.addSelf(selfLink.toString()); this.links.addEvents(paymentEventsLink.toString()); this.links.addRefunds(paymentRefundsLink.toString()); @@ -86,7 +88,8 @@ public static PaymentForSearchResult valueOf( paymentResult.getFee(), paymentResult.getNetAmount(), AuthorisationSummaryHelper.includeAuthorisationSummaryWhen3dsRequired(paymentResult.getAuthorisationSummary()), - paymentResult.getAuthorisationMode()); + paymentResult.getAuthorisationMode(), + paymentResult.getExemption()); } public PaymentLinksForSearch getLinks() { diff --git a/src/main/java/uk/gov/pay/api/utils/ExemptionEvaluator.java b/src/main/java/uk/gov/pay/api/utils/ExemptionEvaluator.java new file mode 100644 index 000000000..a7c46af28 --- /dev/null +++ b/src/main/java/uk/gov/pay/api/utils/ExemptionEvaluator.java @@ -0,0 +1,20 @@ +package uk.gov.pay.api.utils; + +import uk.gov.pay.api.model.Exemption; + +import java.util.Optional; + +public class ExemptionEvaluator { + public static Exemption evaluateExemption(Exemption maybeExemption) { + return Optional.ofNullable(maybeExemption) + .map(exemption -> { + if (exemption.getType() != null + && exemption.getType().equals("corporate") + && exemption.getOutcome() != null) { + return exemption; + } + return null; + }) + .orElse(null); + } +} diff --git a/src/test/java/uk/gov/pay/api/it/PaymentsResourceGetIT.java b/src/test/java/uk/gov/pay/api/it/PaymentsResourceGetIT.java index 78ae2067c..38d0cb915 100644 --- a/src/test/java/uk/gov/pay/api/it/PaymentsResourceGetIT.java +++ b/src/test/java/uk/gov/pay/api/it/PaymentsResourceGetIT.java @@ -7,6 +7,8 @@ import uk.gov.pay.api.model.Address; import uk.gov.pay.api.model.AuthorisationSummary; import uk.gov.pay.api.model.CardDetailsFromResponse; +import uk.gov.pay.api.model.Exemption; +import uk.gov.pay.api.model.ExemptionOutcome; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.pay.api.model.PaymentState; import uk.gov.pay.api.model.RefundSummary; @@ -835,6 +837,94 @@ public void getRejectedPaymentShouldIncludeCanRetryWhenThroughConnector() { .body("state.can_retry", is(true)); } + @Test + public void getPaymentShouldIncludeExemptionWhenCorporateExemptionRequestedThroughConnector() { + ExemptionOutcome exemptionOutcome = new ExemptionOutcome("honoured"); + Exemption exemption = new Exemption(true, "corporate", exemptionOutcome); + connectorMockClient.respondWithChargeFound(CHARGE_TOKEN_ID, GATEWAY_ACCOUNT_ID, + getConnectorCharge() + .withExemption(exemption) + .build()); + + getPaymentResponse(CHARGE_ID) + .statusCode(200) + .contentType(JSON) + .body("exemption.requested", is(true)) + .body("exemption.type", is("corporate")) + .body("exemption.outcome.result", is("honoured")); + } + + @Test + public void getPaymentShouldNotIncludeExemptionWhenNoExemptionTypeThroughConnector() { + ExemptionOutcome exemptionOutcome = new ExemptionOutcome("honoured"); + Exemption exemption = new Exemption(true, null, exemptionOutcome); + connectorMockClient.respondWithChargeFound(CHARGE_TOKEN_ID, GATEWAY_ACCOUNT_ID, + getConnectorCharge() + .withExemption(exemption) + .build()); + + getPaymentResponse(CHARGE_ID) + .statusCode(200) + .contentType(JSON) + .body("exemption", is(nullValue())); + } + + @Test + public void getPaymentShouldNotIncludeExemptionWhenNoExemptionThroughConnector() { + connectorMockClient.respondWithChargeFound(CHARGE_TOKEN_ID, GATEWAY_ACCOUNT_ID, + getConnectorCharge() + .build()); + + getPaymentResponse(CHARGE_ID) + .statusCode(200) + .contentType(JSON) + .body("exemption", is(nullValue())); + } + + @Test + public void getPaymentShouldIncludeExemptionWhenCorporateExemptionRequestedThroughLedger() { + ExemptionOutcome exemptionOutcome = new ExemptionOutcome("honoured"); + Exemption exemption = new Exemption(true, "corporate", exemptionOutcome); + ledgerMockClient.respondWithTransaction(CHARGE_ID, + getLedgerTransaction() + .withExemption(exemption) + .build()); + + getPaymentResponse(CHARGE_ID, LEDGER_ONLY_STRATEGY) + .statusCode(200) + .contentType(JSON) + .body("exemption.requested", is(true)) + .body("exemption.type", is("corporate")) + .body("exemption.outcome.result", is("honoured")); + } + + @Test + public void getPaymentShouldNotIncludeExemptionWhenNoExemptionTypeThroughLedger() { + ExemptionOutcome exemptionOutcome = new ExemptionOutcome("honoured"); + Exemption exemption = new Exemption(true, null, exemptionOutcome); + ledgerMockClient.respondWithTransaction(CHARGE_ID, + getLedgerTransaction() + .withExemption(exemption) + .build()); + + getPaymentResponse(CHARGE_ID) + .statusCode(200) + .contentType(JSON) + .body("exemption", is(nullValue())); + } + + @Test + public void getPaymentShouldNotIncludeExemptionWhenNoExemptionThroughLedger() { + ledgerMockClient.respondWithTransaction(CHARGE_ID, + getLedgerTransaction() + .build()); + + getPaymentResponse(CHARGE_ID) + .statusCode(200) + .contentType(JSON) + .body("exemption", is(nullValue())); + } + private ChargeResponseFromConnector.ChargeResponseFromConnectorBuilder getConnectorCharge() { return aCreateOrGetChargeResponseFromConnector() .withAmount(AMOUNT) diff --git a/src/test/java/uk/gov/pay/api/it/PaymentsResourceSearchIT.java b/src/test/java/uk/gov/pay/api/it/PaymentsResourceSearchIT.java index 816c9180d..d82b7205e 100644 --- a/src/test/java/uk/gov/pay/api/it/PaymentsResourceSearchIT.java +++ b/src/test/java/uk/gov/pay/api/it/PaymentsResourceSearchIT.java @@ -10,6 +10,8 @@ import uk.gov.pay.api.it.fixtures.PaymentNavigationLinksFixture; import uk.gov.pay.api.model.Address; import uk.gov.pay.api.model.CardDetailsFromResponse; +import uk.gov.pay.api.model.Exemption; +import uk.gov.pay.api.model.ExemptionOutcome; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.pay.api.model.Wallet; import uk.gov.pay.api.utils.PublicAuthMockClient; @@ -407,6 +409,28 @@ public void shouldReturnSettledDate_whenLedgerReturnsSettledDateInSettlementSumm .body("results[0].settlement_summary.capture_submit_time", is(DEFAULT_CAPTURE_SUBMIT_TIME)); } + @Test + public void searchPayments_getsResults_withExemption() { + ExemptionOutcome exemptionOutcome = new ExemptionOutcome("honoured"); + Exemption exemption = new Exemption(true, "corporate", exemptionOutcome); + String payments = aPaginatedPaymentSearchResult() + .withPayments(aSuccessfulSearchPayment() + .withExemption(exemption) + .withNumberOfResults(1) + .getResults()) + .build(); + + ledgerMockClient.respondOk_whenSearchCharges(payments); + + ImmutableMap queryParams = ImmutableMap.of(); + searchPayments(queryParams) + .statusCode(200) + .contentType(JSON) + .body("results[0].exemption.requested", is(true)) + .body("results[0].exemption.type", is("corporate")) + .body("results[0].exemption.outcome.result", is("honoured")); + } + private Matcher>> matchesState(final String state) { return new TypeSafeMatcher<>() { @Override diff --git a/src/test/java/uk/gov/pay/api/it/fixtures/PaymentResultBuilder.java b/src/test/java/uk/gov/pay/api/it/fixtures/PaymentResultBuilder.java index 9431af8e9..226078dfd 100644 --- a/src/test/java/uk/gov/pay/api/it/fixtures/PaymentResultBuilder.java +++ b/src/test/java/uk/gov/pay/api/it/fixtures/PaymentResultBuilder.java @@ -1,6 +1,7 @@ package uk.gov.pay.api.it.fixtures; import uk.gov.pay.api.model.CardDetailsFromResponse; +import uk.gov.pay.api.model.Exemption; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.service.payments.commons.model.AuthorisationMode; import uk.gov.service.payments.commons.model.SupportedLanguage; @@ -144,6 +145,7 @@ protected static class TestPayment { public String agreement_id; public String authorisation_mode; public String wallet_type; + public Exemption exemption; } protected static class TestPaymentState { @@ -217,6 +219,7 @@ protected TestPaymentRejectedState(String status, String code, String message, b protected AuthorisationSummary authorisationSummary; protected AuthorisationMode authorisationMode = AuthorisationMode.WEB; protected String walletType = null; + protected Exemption exemption; public abstract String build(); @@ -263,6 +266,7 @@ private TestPayment defaultPaymentResult() { payment.authorisation_summary = authorisationSummary; payment.authorisation_mode = authorisationMode.getName(); payment.wallet_type = walletType; + payment.exemption = exemption; return payment; } diff --git a/src/test/java/uk/gov/pay/api/it/fixtures/PaymentSearchResultBuilder.java b/src/test/java/uk/gov/pay/api/it/fixtures/PaymentSearchResultBuilder.java index 09df681c1..c8ad6fa53 100644 --- a/src/test/java/uk/gov/pay/api/it/fixtures/PaymentSearchResultBuilder.java +++ b/src/test/java/uk/gov/pay/api/it/fixtures/PaymentSearchResultBuilder.java @@ -4,6 +4,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import uk.gov.pay.api.model.CardDetailsFromResponse; +import uk.gov.pay.api.model.Exemption; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.service.payments.commons.model.AuthorisationMode; @@ -125,6 +126,11 @@ public PaymentSearchResultBuilder withWalletType(String walletType) { return this; } + public PaymentSearchResultBuilder withExemption(Exemption exemption) { + this.exemption = exemption; + return this; + } + public List getResults() { List results = newArrayList(); for (int i = 0; i < noOfResults; i++) { diff --git a/src/test/java/uk/gov/pay/api/it/fixtures/PaymentSingleResultBuilder.java b/src/test/java/uk/gov/pay/api/it/fixtures/PaymentSingleResultBuilder.java index c902d1f81..78ad1b5d2 100644 --- a/src/test/java/uk/gov/pay/api/it/fixtures/PaymentSingleResultBuilder.java +++ b/src/test/java/uk/gov/pay/api/it/fixtures/PaymentSingleResultBuilder.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.google.gson.Gson; import uk.gov.pay.api.model.CardDetailsFromResponse; +import uk.gov.pay.api.model.Exemption; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.pay.api.model.PaymentState; import uk.gov.service.payments.commons.model.AuthorisationMode; @@ -152,6 +153,11 @@ public PaymentSingleResultBuilder withWalletType(String walletType) { return this; } + public PaymentSingleResultBuilder withExemption(Exemption exemption) { + this.exemption = exemption; + return this; + } + public String build() { TestPayment result = getPayment(); return new Gson().toJson(result, new TypeReference() {}.getType()); diff --git a/src/test/java/uk/gov/pay/api/service/GetPaymentServiceLedgerPactTest.java b/src/test/java/uk/gov/pay/api/service/GetPaymentServiceLedgerPactTest.java index 7b47c7b17..372f6fe71 100644 --- a/src/test/java/uk/gov/pay/api/service/GetPaymentServiceLedgerPactTest.java +++ b/src/test/java/uk/gov/pay/api/service/GetPaymentServiceLedgerPactTest.java @@ -197,4 +197,18 @@ public void testGetRejectedPaymentWithNoRetryTrueFromLedger() { assertThat(paymentResponse.getState().getCanRetry(), is(true)); assertThat(paymentResponse.getAuthorisationMode(), is(AuthorisationMode.AGREEMENT)); } + + @Test + @PactVerification({"ledger"}) + @Pacts(pacts = {"publicapi-ledger-get-payment-with-honoured-exemption"}) + public void testGetPaymentWithExemptionFromLedger() { + Account account = new Account(ACCOUNT_ID, TokenPaymentType.CARD, tokenLink); + + PaymentWithAllLinks paymentResponse = getPaymentService.getPayment(account, CHARGE_ID_NON_EXISTENT_IN_CONNECTOR); + + assertThat(paymentResponse.getExemption(), is(notNullValue())); + assertThat(paymentResponse.getExemption().getRequested(), is(true)); + assertThat(paymentResponse.getExemption().getType(), is("corporate")); + assertThat(paymentResponse.getExemption().getOutcome().getResult(), is("honoured")); + } } diff --git a/src/test/java/uk/gov/pay/api/utils/ExemptionEvaluatorTest.java b/src/test/java/uk/gov/pay/api/utils/ExemptionEvaluatorTest.java new file mode 100644 index 000000000..770e58358 --- /dev/null +++ b/src/test/java/uk/gov/pay/api/utils/ExemptionEvaluatorTest.java @@ -0,0 +1,41 @@ +package uk.gov.pay.api.utils; + +import org.junit.jupiter.api.Test; +import uk.gov.pay.api.model.Exemption; +import uk.gov.pay.api.model.ExemptionOutcome; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +class ExemptionEvaluatorTest { + + @Test + void shouldReturnNull_whenExemptionIsNull() { + assertThat(ExemptionEvaluator.evaluateExemption(null), is(nullValue())); + } + + @Test + void shouldReturnNull_whenOutcomeIsNotKnown() { + Exemption exemption = new Exemption(true, "corporate", null); + assertThat(ExemptionEvaluator.evaluateExemption(exemption), is(nullValue())); + } + + @Test + void shouldReturnNull_whenTypeIsNull() { + Exemption exemption = new Exemption(true, null, new ExemptionOutcome("honoured")); + assertThat(ExemptionEvaluator.evaluateExemption(exemption), is(nullValue())); + } + + @Test + void shouldReturnExemption_whenTypeIsCorporateAndOutcomeIsKnown() { + Exemption exemption = new Exemption(true, "corporate", new ExemptionOutcome("rejected")); + assertThat(ExemptionEvaluator.evaluateExemption(exemption), is(exemption)); + } + + @Test + void shouldReturnNull_whenRequestedIsNull() { + Exemption exemption = new Exemption(false, null, null); + assertThat(ExemptionEvaluator.evaluateExemption(exemption), is(nullValue())); + } +} \ No newline at end of file diff --git a/src/test/java/uk/gov/pay/api/utils/mocks/ChargeResponseFromConnector.java b/src/test/java/uk/gov/pay/api/utils/mocks/ChargeResponseFromConnector.java index e9a07c886..96bcda019 100644 --- a/src/test/java/uk/gov/pay/api/utils/mocks/ChargeResponseFromConnector.java +++ b/src/test/java/uk/gov/pay/api/utils/mocks/ChargeResponseFromConnector.java @@ -2,6 +2,7 @@ import uk.gov.pay.api.model.AuthorisationSummary; import uk.gov.pay.api.model.CardDetailsFromResponse; +import uk.gov.pay.api.model.Exemption; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.pay.api.model.PaymentState; import uk.gov.pay.api.model.RefundSummary; @@ -34,6 +35,7 @@ public class ChargeResponseFromConnector { private final String agreementId; private final AuthorisationMode authorisationMode; private final String walletType; + private final Exemption exemption; public Long getAmount() { return amount; @@ -161,6 +163,10 @@ public AuthorisationMode getAuthorisationMode() { public String getWalletType() { return walletType; } + public Exemption getExemption() { + return exemption; + } + private ChargeResponseFromConnector(ChargeResponseFromConnectorBuilder builder) { this.amount = builder.amount; this.chargeId = builder.chargeId; @@ -194,6 +200,7 @@ private ChargeResponseFromConnector(ChargeResponseFromConnectorBuilder builder) this.agreementId = builder.agreementId; this.authorisationMode = builder.authorisationMode; this.walletType = builder.walletType; + this.exemption = builder.exemption; } public static final class ChargeResponseFromConnectorBuilder { @@ -215,6 +222,7 @@ public static final class ChargeResponseFromConnectorBuilder { private String agreementId; private AuthorisationMode authorisationMode = AuthorisationMode.WEB; private String walletType = null; + private Exemption exemption = null; private ChargeResponseFromConnectorBuilder() { } @@ -249,7 +257,8 @@ public static ChargeResponseFromConnectorBuilder aCreateOrGetChargeResponseFromC .withAuthorisationSummary(responseFromConnector.getAuthorisationSummary()) .withAgreementId(responseFromConnector.getAgreementId()) .withAuthorisationMode(responseFromConnector.getAuthorisationMode()) - .withWalletType(responseFromConnector.walletType); + .withWalletType(responseFromConnector.walletType) + .withExemption(responseFromConnector.exemption); } public ChargeResponseFromConnectorBuilder withAmount(long amount) { @@ -421,6 +430,11 @@ public ChargeResponseFromConnectorBuilder withWalletType(String walletType) { this.walletType = walletType; return this; } + + public ChargeResponseFromConnectorBuilder withExemption(Exemption exemption) { + this.exemption = exemption; + return this; + } } } diff --git a/src/test/java/uk/gov/pay/api/utils/mocks/MockHelperFunctions.java b/src/test/java/uk/gov/pay/api/utils/mocks/MockHelperFunctions.java index ffee2f82c..2668b28b0 100644 --- a/src/test/java/uk/gov/pay/api/utils/mocks/MockHelperFunctions.java +++ b/src/test/java/uk/gov/pay/api/utils/mocks/MockHelperFunctions.java @@ -132,6 +132,7 @@ static String buildChargeResponse(ChargeResponseFromConnector responseFromConnec ofNullable(responseFromConnector.getNetAmount()).ifPresent(resultBuilder::withNetAmount); ofNullable(responseFromConnector.getAuthorisationSummary()).ifPresent(resultBuilder::withAuthorisationSummary); ofNullable(responseFromConnector.getWalletType()).ifPresent(resultBuilder::withWalletType); + ofNullable(responseFromConnector.getExemption()).ifPresent(resultBuilder::withExemption); responseFromConnector.getMetadata().ifPresent(resultBuilder::withMetadata); return resultBuilder.build(); diff --git a/src/test/java/uk/gov/pay/api/utils/mocks/TransactionFromLedgerFixture.java b/src/test/java/uk/gov/pay/api/utils/mocks/TransactionFromLedgerFixture.java index ca6339e9a..47828505a 100644 --- a/src/test/java/uk/gov/pay/api/utils/mocks/TransactionFromLedgerFixture.java +++ b/src/test/java/uk/gov/pay/api/utils/mocks/TransactionFromLedgerFixture.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import uk.gov.pay.api.model.AuthorisationSummary; import uk.gov.pay.api.model.CardDetailsFromResponse; +import uk.gov.pay.api.model.Exemption; import uk.gov.pay.api.model.PaymentConnectorResponseLink; import uk.gov.pay.api.model.PaymentSettlementSummary; import uk.gov.pay.api.model.PaymentState; @@ -43,6 +44,7 @@ public class TransactionFromLedgerFixture { private AuthorisationSummary authorisationSummary; private AuthorisationMode authorisationMode; private final Optional walletType; + private Exemption exemption; public String getReturnUrl() { return returnUrl; @@ -126,6 +128,10 @@ public AuthorisationMode getAuthorisationMode() { public Optional getWalletType() { return walletType; } + public Exemption getExemption() { + return exemption; + } + public TransactionFromLedgerFixture(TransactionFromLedgerBuilder builder) { this.amount = builder.amount; this.state = builder.state; @@ -152,6 +158,7 @@ public TransactionFromLedgerFixture(TransactionFromLedgerBuilder builder) { this.authorisationSummary = builder.authorisationSummary; this.authorisationMode = builder.authorisationMode; this.walletType = builder.walletType == null || builder.walletType.isEmpty() ? Optional.empty() : Optional.of(builder.walletType); + this.exemption = builder.exemption; } public Long getAmount() { @@ -197,6 +204,7 @@ public static final class TransactionFromLedgerBuilder { private AuthorisationSummary authorisationSummary; private AuthorisationMode authorisationMode = AuthorisationMode.WEB; private String walletType; + private Exemption exemption; private TransactionFromLedgerBuilder() { @@ -331,6 +339,11 @@ public TransactionFromLedgerBuilder withWalletType(String walletType) { return this; } + public TransactionFromLedgerBuilder withExemption(Exemption exemption) { + this.exemption = exemption; + return this; + } + public TransactionFromLedgerFixture build() { return new TransactionFromLedgerFixture(this); } diff --git a/src/test/resources/pacts/publicapi-ledger-get-payment-with-honoured-exemption.json b/src/test/resources/pacts/publicapi-ledger-get-payment-with-honoured-exemption.json new file mode 100644 index 000000000..9689a502a --- /dev/null +++ b/src/test/resources/pacts/publicapi-ledger-get-payment-with-honoured-exemption.json @@ -0,0 +1,96 @@ +{ + "consumer": { + "name": "publicapi" + }, + "provider": { + "name": "ledger" + }, + "interactions": [ + { + "description": "get a charge request with honoured corporate exemption", + "providerStates": [ + { + "name": "a transaction with honoured corporate exemption exists", + "params": { + "account_id": "123456", + "charge_id": "ch_123abc456xyz" + } + } + ], + "request": { + "method": "GET", + "path": "/v1/transaction/ch_123abc456xyz", + "query": { + "account_id": ["123456"], + "transaction_type": ["PAYMENT"], + "status_version": ["1"] + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "amount": 100, + "state": { + "finished": true, + "status": "success" + }, + "description": "Test description", + "reference": "aReference", + "language": "en", + "transaction_id": "ch_123abc456xyz", + "return_url": "https://somewhere.gov.uk/rainbow/1", + "payment_provider": "sandbox", + "created_date": "2018-10-16T10:46:02.121Z", + "refund_summary": { + "status": "available", + "user_external_id": null, + "amount_available": 100, + "amount_submitted": 0 + }, + "settlement_summary": { + "capture_submit_time": null, + "captured_date": null + }, + "delayed_capture": false, + "exemption": { + "requested": true, + "type": "corporate", + "outcome": { + "result": "honoured" + } + } + }, + "matchingRules": { + "body": { + "$.reference": { + "matchers": [{"match": "type"}] + }, + "$.description": { + "matchers": [{"match": "type"}] + }, + "$.return_url": { + "matchers": [{"match": "type"}] + }, + "$.created_date": { + "matchers": [{ "date": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" }] + }, + "$.refund_summary.amount_available": { + "matchers": [{"match": "type"}] + } + } + } + } + } + ], + "metadata": { + "pact-specification": { + "version": "3.0.0" + }, + "pact-jvm": { + "version": "3.5.16" + } + } +}