diff --git a/src/main/java/uk/gov/pay/connector/charge/service/ChargeService.java b/src/main/java/uk/gov/pay/connector/charge/service/ChargeService.java index d9da8f1ab..c09d0a2da 100644 --- a/src/main/java/uk/gov/pay/connector/charge/service/ChargeService.java +++ b/src/main/java/uk/gov/pay/connector/charge/service/ChargeService.java @@ -125,7 +125,6 @@ import static net.logstash.logback.argument.StructuredArguments.kv; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit.LUHN_CHECK_DIGIT; -import static org.apache.http.HttpStatus.SC_CONFLICT; import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY; import static uk.gov.pay.connector.charge.model.ChargeResponse.aChargeResponseBuilder; import static uk.gov.pay.connector.charge.model.FrontendChargeResponse.aFrontendChargeResponse; @@ -149,12 +148,13 @@ import static uk.gov.service.payments.commons.model.AuthorisationMode.MOTO_API; import static uk.gov.service.payments.commons.model.ErrorIdentifier.AGREEMENT_NOT_ACTIVE; import static uk.gov.service.payments.commons.model.ErrorIdentifier.AGREEMENT_NOT_FOUND; +import static uk.gov.service.payments.commons.model.ErrorIdentifier.AMOUNT_BELOW_MINIMUM; import static uk.gov.service.payments.commons.model.ErrorIdentifier.CARD_NUMBER_IN_PAYMENT_LINK_REFERENCE_REJECTED; import static uk.gov.service.payments.commons.model.ErrorIdentifier.GENERIC; -import static uk.gov.service.payments.commons.model.ErrorIdentifier.IDEMPOTENCY_KEY_USED; import static uk.gov.service.payments.commons.model.ErrorIdentifier.MOTO_NOT_ALLOWED; import static uk.gov.service.payments.commons.model.ErrorIdentifier.NON_HTTPS_RETURN_URL_NOT_ALLOWED_FOR_A_LIVE_ACCOUNT; import static uk.gov.service.payments.commons.model.Source.CARD_AGENT_INITIATED_MOTO; +import static uk.gov.service.payments.commons.model.Source.CARD_API; import static uk.gov.service.payments.commons.model.Source.CARD_PAYMENT_LINK; public class ChargeService { @@ -318,9 +318,7 @@ public Optional create(ChargeCreateRequest chargeRequest, Long a private Optional createCharge(ChargeCreateRequest chargeRequest, Long accountId, String idempotencyKey) { return gatewayAccountDao.findById(accountId).map(gatewayAccount -> { checkIfGatewayAccountDisabled(gatewayAccount); - - checkIfZeroAmountAllowed(chargeRequest.getAmount(), gatewayAccount); - + var authorisationMode = chargeRequest.getAuthorisationMode(); if (authorisationMode == MOTO_API) { @@ -342,7 +340,9 @@ private Optional createCharge(ChargeCreateRequest chargeRequest, L GatewayAccountCredentialsEntity gatewayAccountCredential = getGatewayAccountCredentialsEntity(chargeRequest, gatewayAccount); - + + checkIfAmountBelowMinimum(chargeRequest.getSource(), chargeRequest.getAmount(), gatewayAccount, gatewayAccountCredential.getPaymentProvider()); + var agreementEntity = Optional.ofNullable(chargeRequest.getAgreementId()).map(agreementId -> agreementDao.findByExternalIdAndGatewayAccountId(chargeRequest.getAgreementId(), gatewayAccount.getId()) .orElseThrow(() -> new ChargeException("Agreement with ID [" + chargeRequest.getAgreementId() + "] not found.", AGREEMENT_NOT_FOUND, HttpStatus.SC_BAD_REQUEST))); @@ -1168,20 +1168,26 @@ private void checkMotoApiAuthorisationModeAllowed(GatewayAccountEntity gatewayAc throw new AuthorisationApiNotAllowedForGatewayAccountException(gatewayAccount.getId()); } } - - + private void checkIfGatewayAccountDisabled(GatewayAccountEntity gatewayAccount) { if (gatewayAccount.isDisabled()) { throw new GatewayAccountDisabledException("Attempt to create a charge for a disabled gateway account"); } } + private void checkIfAmountBelowMinimum(Source source, Long amount, GatewayAccountEntity gatewayAccount, String paymentProvider) { + if (source == CARD_API && amount < 30 && PaymentGatewayName.valueFrom(paymentProvider) == PaymentGatewayName.STRIPE) { + throw new ChargeException("Payments under 30 pence are not allowed for Stripe accounts", AMOUNT_BELOW_MINIMUM, SC_UNPROCESSABLE_ENTITY); + } + checkIfZeroAmountAllowed(amount, gatewayAccount); + } + private void checkIfZeroAmountAllowed(Long amount, GatewayAccountEntity gatewayAccount) { if (amount == 0L && !gatewayAccount.isAllowZeroAmount()) { throw new ZeroAmountNotAllowedForGatewayAccountException(gatewayAccount.getId()); } } - + private void checkIfMotoPaymentsAllowed(boolean moto, GatewayAccountEntity gatewayAccount) { if (moto && !gatewayAccount.isAllowMoto()) { throw new ChargeException("MOTO payments are not enabled for this gateway account", MOTO_NOT_ALLOWED, SC_UNPROCESSABLE_ENTITY); diff --git a/src/test/java/uk/gov/pay/connector/charge/service/ChargeServiceCreateTest.java b/src/test/java/uk/gov/pay/connector/charge/service/ChargeServiceCreateTest.java index 80fd37dc3..ecbd7f1a1 100644 --- a/src/test/java/uk/gov/pay/connector/charge/service/ChargeServiceCreateTest.java +++ b/src/test/java/uk/gov/pay/connector/charge/service/ChargeServiceCreateTest.java @@ -102,6 +102,7 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentCaptor.forClass; import static org.mockito.ArgumentMatchers.any; @@ -729,6 +730,26 @@ void shouldThrowException_whenGatewayAccountDisabled() { verify(mockedChargeDao, never()).persist(any(ChargeEntity.class)); } + @Test + void shouldThrowException_whenPaymentProviderIsStripeAndAmountUnder30Pence() { + GatewayAccountCredentialsEntity stripeGatewayAccountCredentialsEntity = GatewayAccountCredentialsEntityFixture + .aGatewayAccountCredentialsEntity() + .withGatewayAccountEntity(gatewayAccount) + .withPaymentProvider("stripe") + .withCredentials(Map.of()) + .withState(GatewayAccountCredentialState.ACTIVE) + .build(); + ChargeCreateRequest request = requestBuilder + .withAmount(29) + .withSource(CARD_API) + .build(); + when(mockedGatewayAccountDao.findById(GATEWAY_ACCOUNT_ID)).thenReturn(Optional.of(gatewayAccount)); + when(mockGatewayAccountCredentialsService.getCurrentOrActiveCredential(gatewayAccount)).thenReturn(stripeGatewayAccountCredentialsEntity); + + assertThrows(ChargeException.class, () -> chargeService.create(request, GATEWAY_ACCOUNT_ID, mockedUriInfo, null)); + verify(mockedChargeDao, never()).persist(any(ChargeEntity.class)); + } + @Test void shouldCreateNewChargeAndPersistIdempotency() throws URISyntaxException { String idempotencyKey = "idempotency-key"; diff --git a/src/test/java/uk/gov/pay/connector/it/resources/ChargesApiResourceCreateIT.java b/src/test/java/uk/gov/pay/connector/it/resources/ChargesApiResourceCreateIT.java index 58f9181db..3b832e999 100644 --- a/src/test/java/uk/gov/pay/connector/it/resources/ChargesApiResourceCreateIT.java +++ b/src/test/java/uk/gov/pay/connector/it/resources/ChargesApiResourceCreateIT.java @@ -20,6 +20,7 @@ import uk.gov.pay.connector.gateway.PaymentGatewayName; import uk.gov.pay.connector.gatewayaccount.model.GatewayAccountType; import uk.gov.pay.connector.it.base.ITestBaseExtension; +import uk.gov.pay.connector.it.dao.DatabaseFixtures; import uk.gov.service.payments.commons.model.ErrorIdentifier; import javax.ws.rs.core.Response.Status; @@ -27,6 +28,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -364,7 +366,7 @@ void when_moto_is_true_and_moto_not_allowed_for_account() { } @Test - void when_amount_is_zero_and_account_does_not_allow_zero_zmount() { + void when_amount_is_zero_and_account_does_not_allow_zero_amount() { //by default, gateway account does not have zero amount enabled app.givenSetup() .body(toJson(Map.of( @@ -380,6 +382,30 @@ void when_amount_is_zero_and_account_does_not_allow_zero_zmount() { .body("message", contains("Zero amount charges are not enabled for this gateway account")) .body("error_identifier", is(ErrorIdentifier.ZERO_AMOUNT_NOT_ALLOWED.toString())); } + + @Test + void when_amount_is_under_30p_for_api_payment_for_Stripe_account() { + DatabaseFixtures.TestAccount stripeTestAccount = app.getDatabaseFixtures() + .aTestAccount() + .withPaymentProvider("stripe") + .withCredentials(Collections.singletonMap("stripe_account_id", "acct_123example123")) + .insert(); + + app.givenSetup() + .body(toJson(Map.of( + "amount", 29, + "reference", "Test reference", + "description", "Test description", + "return_url", "http://service.local/success-page/", + "source", "CARD_API" + ))) + .post(format("/v1/api/accounts/%s/charges", stripeTestAccount.getAccountId())) + .then() + .statusCode(422) + .contentType(JSON) + .body("message", contains("Payments under 30 pence are not allowed for Stripe accounts")) + .body("error_identifier", is(ErrorIdentifier.AMOUNT_BELOW_MINIMUM.toString())); + } } @Test