Use the cheapest default token when multiple are available (#2990)

Previously we would just use the first one we found. This is a valid
behavior, but we want to change it so that we apply the cheapest default
if multiple are available (this way we avoid having to go back after the
fact and give refunds).
This commit is contained in:
gbrodman
2026-03-27 17:19:38 +00:00
committed by GitHub
parent 3513364c97
commit a129a0dc21
16 changed files with 215 additions and 74 deletions
@@ -1577,7 +1577,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistHosts();
setupDefaultToken("aaaaa", 0, "TheRegistrar");
setupDefaultTokenWithDiscount();
runTest_defaultToken("aaaaa");
runTest_defaultToken("bbbbb");
}
@Test
@@ -1243,7 +1243,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setDiscountFraction(0.5)
.setDiscountFraction(0.9)
.setDiscountYears(1)
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
@@ -1271,8 +1271,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey())
.isEqualTo(defaultToken1.getToken());
// Price is 50% off the first year only. Non-discounted price is $11.
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 16.5));
// Price is 90% off the first year only. Non-discounted price is $11.
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 12.10));
}
@Test
@@ -1412,7 +1412,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
}
@Test
void testSuccess_onlyUsesFirstValidToken() throws Exception {
void testSuccess_usesCheapestValidToken() throws Exception {
setEppInput("domain_renew.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistDomain();
AllocationToken defaultToken1 =
@@ -1459,10 +1459,9 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
BillingEvent billingEvent =
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey())
.isEqualTo(defaultToken2.getToken());
// Price is 50% off the first year only. Non-discounted price is $11.
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 16.5));
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo("ccccc");
// Price is 75% off the first year only. Non-discounted price is $11.
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 13.75));
}
@Test
@@ -35,6 +35,8 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.net.InternetDomainName;
import google.registry.flows.EppException;
import google.registry.flows.custom.DomainPricingCustomLogic;
import google.registry.flows.domain.DomainPricingLogic;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.NonexistentAllocationTokenException;
@@ -65,6 +67,9 @@ class AllocationTokenFlowUtilsTest {
private final AllocationTokenExtension allocationTokenExtension =
mock(AllocationTokenExtension.class);
private final DomainPricingLogic domainPricingLogic =
new DomainPricingLogic(new DomainPricingCustomLogic(null, null, null));
private Tld tld;
@BeforeEach
@@ -140,7 +145,9 @@ class AllocationTokenFlowUtilsTest {
Optional.of(allocationTokenExtension),
tld,
"example.tld",
CommandName.CREATE))
CommandName.CREATE,
Optional.of(1),
domainPricingLogic))
.hasValue(token);
}
@@ -154,7 +161,9 @@ class AllocationTokenFlowUtilsTest {
Optional.empty(),
tld,
"example.tld",
CommandName.CREATE))
CommandName.CREATE,
Optional.of(1),
domainPricingLogic))
.hasValue(defaultToken);
}
@@ -176,7 +185,9 @@ class AllocationTokenFlowUtilsTest {
Optional.of(allocationTokenExtension),
tld,
"example.tld",
CommandName.CREATE))
CommandName.CREATE,
Optional.of(1),
domainPricingLogic))
.hasValue(defaultToken);
}
@@ -299,7 +310,9 @@ class AllocationTokenFlowUtilsTest {
Optional.of(allocationTokenExtension),
tld,
"example.tld",
CommandName.CREATE));
CommandName.CREATE,
Optional.of(1),
domainPricingLogic));
}
@Test
@@ -311,7 +324,9 @@ class AllocationTokenFlowUtilsTest {
Optional.empty(),
tld,
"example.tld",
CommandName.CREATE))
CommandName.CREATE,
Optional.of(1),
domainPricingLogic))
.isEmpty();
}
@@ -329,7 +344,93 @@ class AllocationTokenFlowUtilsTest {
Optional.of(allocationTokenExtension),
tld,
"example.tld",
CommandName.CREATE));
CommandName.CREATE,
Optional.of(1),
domainPricingLogic));
}
@Test
void testSuccess_default_cheaperTokenUsed() throws Exception {
AllocationToken cheaperToken =
persistResource(
new AllocationToken.Builder()
.setToken("cheaperToken")
.setDiscountFraction(0.5)
.setAllowedTlds(ImmutableSet.of("tld"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setTokenType(DEFAULT_PROMO)
.build());
AllocationToken moreExpensiveToken =
persistResource(
new AllocationToken.Builder()
.setToken("moreExpensiveToken")
.setDiscountFraction(0.1)
.setAllowedTlds(ImmutableSet.of("tld"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setTokenType(DEFAULT_PROMO)
.build());
// List the more expensive token first to ensure that we don't just pick the first valid one
tld =
persistResource(
tld.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(moreExpensiveToken.createVKey(), cheaperToken.createVKey()))
.build());
assertThat(
AllocationTokenFlowUtils.loadTokenFromExtensionOrGetDefault(
"TheRegistrar",
clock.nowUtc(),
Optional.empty(),
tld,
"example.tld",
CommandName.CREATE,
Optional.of(1),
domainPricingLogic))
.hasValue(cheaperToken);
}
@Test
void testSuccess_default_twoYearsIsCheaper() throws Exception {
AllocationToken longerToken =
persistResource(
new AllocationToken.Builder()
.setToken("longerToken")
.setDiscountFraction(0.4)
.setDiscountYears(2)
.setAllowedTlds(ImmutableSet.of("tld"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setTokenType(DEFAULT_PROMO)
.build());
AllocationToken shorterToken =
persistResource(
new AllocationToken.Builder()
.setToken("shorterToken")
.setDiscountFraction(0.5)
.setDiscountYears(1)
.setAllowedTlds(ImmutableSet.of("tld"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setTokenType(DEFAULT_PROMO)
.build());
tld =
persistResource(
tld.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(shorterToken.createVKey(), longerToken.createVKey()))
.build());
// The token with the smaller discount fraction should be chosen because it runs for 2 years
assertThat(
AllocationTokenFlowUtils.loadTokenFromExtensionOrGetDefault(
"TheRegistrar",
clock.nowUtc(),
Optional.empty(),
tld,
"example.tld",
CommandName.CREATE,
Optional.of(2),
domainPricingLogic))
.hasValue(longerToken);
}
private AllocationToken persistDefaultToken() {