From fe222bbdcfc85856050934a18d01051244828133 Mon Sep 17 00:00:00 2001 From: gbrodman Date: Wed, 24 Jun 2026 11:55:01 -0400 Subject: [PATCH] Fix a couple edge cases with token pricing (#3103) It's unlikely we'd run into this because our tokens are generally just valid for one year, but we should fix these regardless --- .../flows/domain/DomainPricingLogic.java | 7 ++-- .../flows/domain/DomainPricingLogicTest.java | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java index 77b722e79..df65c8e34 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java +++ b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java @@ -283,7 +283,7 @@ public final class DomainPricingLogic { return tld.getStandardRenewCost(dateTime).multipliedBy(years); } if (token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.SPECIFIED)) { - return token.getRenewalPrice().get(); + return token.getRenewalPrice().get().multipliedBy(years); } } return getDomainCostWithDiscount( @@ -328,12 +328,13 @@ public final class DomainPricingLogic { // Apply the allocation token discount, if applicable. if (token.getDiscountPrice().isPresent() && tld.getCurrency().equals(token.getDiscountPrice().get().getCurrencyUnit())) { - int nonDiscountedYears = Math.max(0, years - token.getDiscountYears()); + int discountedYears = Math.min(years, token.getDiscountYears()); + int nonDiscountedYears = years - discountedYears; totalDomainFlowCost = token .getDiscountPrice() .get() - .multipliedBy(token.getDiscountYears()) + .multipliedBy(discountedYears) .plus(subsequentYearCost.orElse(firstYearCost).multipliedBy(nonDiscountedYears)); } else if (token.getDiscountFraction() > 0) { int discountedYears = Math.min(years, token.getDiscountYears()); diff --git a/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java b/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java index 2393d9df1..a2a83c595 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java @@ -203,6 +203,29 @@ public class DomainPricingLogicTest { .build()); } + @Test + void testGetDomainCreatePrice_discountPriceAllocationToken_oneYearCreate_moreDiscountYears() + throws EppException { + AllocationToken allocationToken = + persistResource( + new AllocationToken.Builder() + .setToken("abc123_more_discount") + .setTokenType(SINGLE_USE) + .setDomainName("default.example") + .setDiscountPrice(Money.of(USD, 5)) + .setDiscountYears(2) + .setRegistrationBehavior(RegistrationBehavior.DEFAULT) + .build()); + assertThat( + domainPricingLogic.getCreatePrice( + tld, "default.example", clock.now(), 1, false, false, Optional.of(allocationToken))) + .isEqualTo( + new FeesAndCredits.Builder() + .setCurrency(USD) + .addFeeOrCredit(Fee.create(new BigDecimal("5.00"), CREATE, false)) + .build()); + } + @Test void testGetDomainRenewPrice_oneYear_standardDomain_noBilling_isStandardPrice() throws EppException { @@ -1093,4 +1116,23 @@ public class DomainPricingLogicTest { .getRenewCost()) .isEqualTo(Money.of(USD, 5)); } + + @Test + void testDomainRenewPrice_specifiedToken_multiYear() throws Exception { + AllocationToken allocationToken = + persistResource( + new AllocationToken.Builder() + .setToken("abc123_multi") + .setTokenType(SINGLE_USE) + .setDomainName("premium.example") + .setRenewalPriceBehavior(SPECIFIED) + .setRenewalPrice(Money.of(USD, 5)) + .build()); + assertThat( + domainPricingLogic + .getRenewPrice( + tld, "premium.example", clock.now(), 5, null, Optional.of(allocationToken)) + .getRenewCost()) + .isEqualTo(Money.of(USD, 25)); + } }