Skip to content

Commit

Permalink
PP-13359 do not allow Stripe payments under 30p
Browse files Browse the repository at this point in the history
  • Loading branch information
james-peacock-gds committed Jan 16, 2025
1 parent 3dbb23b commit 1d2db55
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -318,9 +318,7 @@ public Optional<ChargeResponse> create(ChargeCreateRequest chargeRequest, Long a
private Optional<ChargeEntity> 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) {
Expand All @@ -342,7 +340,9 @@ private Optional<ChargeEntity> 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)));
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
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;
import java.sql.Timestamp;
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;
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down

0 comments on commit 1d2db55

Please sign in to comment.