diff --git a/core/src/main/java/google/registry/model/billing/BillingRecurrence.java b/core/src/main/java/google/registry/model/billing/BillingRecurrence.java index c83c11b59..004b2ccd3 100644 --- a/core/src/main/java/google/registry/model/billing/BillingRecurrence.java +++ b/core/src/main/java/google/registry/model/billing/BillingRecurrence.java @@ -191,6 +191,10 @@ public class BillingRecurrence extends BillingBase { ^ instance.renewalPrice == null, "Renewal price can have a value if and only if the renewal price behavior is" + " SPECIFIED"); + if (instance.renewalPrice != null) { + checkArgument( + instance.renewalPrice.isPositiveOrZero(), "SPECIFIED renewal price cannot be negative"); + } instance.recurrenceTimeOfYear = TimeOfYear.fromInstant(instance.eventTime); instance.recurrenceEndTime = Optional.ofNullable(instance.recurrenceEndTime).orElse(END_INSTANT); diff --git a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java index cf629cd31..626647ded 100644 --- a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java +++ b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java @@ -462,6 +462,10 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda getInstance().renewalPriceBehavior.equals(RenewalPriceBehavior.SPECIFIED) == (getInstance().renewalPrice != null), "renewalPrice must be specified iff renewalPriceBehavior is SPECIFIED"); + if (getInstance().renewalPrice != null) { + checkArgument( + getInstance().renewalPrice.isPositiveOrZero(), "Renewal price cannot be negative"); + } if (getInstance().tokenType.equals(TokenType.BULK_PRICING)) { checkArgument( diff --git a/core/src/test/java/google/registry/model/billing/BillingBaseTest.java b/core/src/test/java/google/registry/model/billing/BillingBaseTest.java index 37328b32b..d4c7ff180 100644 --- a/core/src/test/java/google/registry/model/billing/BillingBaseTest.java +++ b/core/src/test/java/google/registry/model/billing/BillingBaseTest.java @@ -890,4 +890,23 @@ public class BillingBaseTest extends EntityTestCase { "Renewal price can have a value if and only if the " + "renewal price behavior is SPECIFIED"); } + + @Test + void testFailure_buildWithSpecifiedRenewalBehavior_negativeRenewalPrice() { + assertThat( + assertThrows( + IllegalArgumentException.class, + () -> + new BillingRecurrence.Builder() + .setDomainHistory(domainHistory) + .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) + .setReason(Reason.RENEW) + .setEventTime(plusYears(now, 1)) + .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) + .setRenewalPrice(Money.of(USD, -100)) + .setRecurrenceEndTime(END_INSTANT) + .build())) + .hasMessageThat() + .isEqualTo("SPECIFIED renewal price cannot be negative"); + } } diff --git a/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java b/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java index 8d26630b1..e914784d5 100644 --- a/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java +++ b/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java @@ -612,6 +612,22 @@ public class AllocationTokenTest extends EntityTestCase { .isEqualTo("renewalPrice must be specified iff renewalPriceBehavior is SPECIFIED"); } + @Test + void testBuild_negativeRenewalPrice() { + assertThat( + assertThrows( + IllegalArgumentException.class, + () -> + new AllocationToken.Builder() + .setToken("abc") + .setTokenType(SINGLE_USE) + .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) + .setRenewalPrice(Money.of(CurrencyUnit.USD, -5)) + .build())) + .hasMessageThat() + .isEqualTo("Renewal price cannot be negative"); + } + @Test void testBuild_registrationBehaviors() { createTld("tld");