1
0
mirror of https://github.com/google/nomulus synced 2026-01-04 20:24:22 +00:00

Add RegistrationBehavior.NONPREMIUM_CREATE (#2481)

When using this token (which must be tied to a particular domain), the
first year price (and only the first year price, i.e. the creation
price) will be the standard price for this TLD. Future years (i.e.
renewals) will continue at the normal premium price.
This commit is contained in:
gbrodman
2024-06-26 16:01:32 -04:00
committed by GitHub
parent 0f0097c15c
commit 54c5a9450d
7 changed files with 193 additions and 36 deletions

View File

@@ -85,6 +85,15 @@ public final class DomainPricingLogic {
createFee = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false);
} else {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
if (allocationToken.isPresent()
&& allocationToken
.get()
.getRegistrationBehavior()
.equals(RegistrationBehavior.NONPREMIUM_CREATE)) {
domainPrices =
DomainPrices.create(
false, tld.getCreateBillingCost(dateTime), domainPrices.getRenewCost());
}
Money domainCreateCost =
getDomainCreateCostWithDiscount(domainPrices, years, allocationToken);
// Apply a sunrise discount if configured and applicable

View File

@@ -115,7 +115,17 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
*/
BYPASS_TLD_STATE,
/** Bypasses most checks and creates the domain as an anchor tenant, with all that implies. */
ANCHOR_TENANT
ANCHOR_TENANT,
/**
* Bypasses the premium list to use the standard creation price. Does not affect the renewal
* price.
*
* <p>This cannot be specified along with a discount fraction, and any renewals (automatic or
* otherwise) will use the premium price for the domain if one exists.
*
* <p>Tokens with this behavior must be tied to a single particular domain.
*/
NONPREMIUM_CREATE
}
/** Type of the token that indicates how and where it should be used. */
@@ -404,6 +414,17 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
checkArgumentNotNull(
getInstance().domainName, "ANCHOR_TENANT tokens must be tied to a domain");
}
if (getInstance().registrationBehavior.equals(RegistrationBehavior.NONPREMIUM_CREATE)) {
checkArgument(
getInstance().discountFraction == 0.0,
"NONPREMIUM_CREATE tokens cannot apply a discount");
checkArgumentNotNull(
getInstance().domainName, "NONPREMIUM_CREATE tokens must be tied to a domain");
checkArgument(
getInstance().allowedEppActions == null
|| getInstance().allowedEppActions.contains(CommandName.CREATE),
"NONPREMIUM_CREATE tokens must allow for CREATE actions");
}
if (getInstance().domainName != null) {
try {
DomainFlowUtils.validateDomainName(getInstance().domainName);

View File

@@ -46,7 +46,7 @@ public interface PremiumPricingEngine {
private Money createCost;
private Money renewCost;
static DomainPrices create(boolean isPremium, Money createCost, Money renewCost) {
public static DomainPrices create(boolean isPremium, Money createCost, Money renewCost) {
DomainPrices instance = new DomainPrices();
instance.isPremium = isPremium;
instance.createCost = createCost;

View File

@@ -3519,6 +3519,22 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertSuccessfulCreate("tld", ImmutableSet.of(), token);
}
@Test
void testSuccess_nonpremiumCreateToken() throws Exception {
createTld("example");
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRegistrationBehavior(RegistrationBehavior.NONPREMIUM_CREATE)
.setDomainName("rich.example")
.build());
setEppInput(
"domain_create_premium_allocationtoken.xml", ImmutableMap.of("YEARS", "1", "FEE", "13.00"));
runFlowAssertResponse(loadFile("domain_create_nonpremium_token_response.xml"));
}
@Test
void testFailure_quietPeriod_defaultTokenPresent() throws Exception {
persistResource(

View File

@@ -55,6 +55,7 @@ import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeHttpSession;
import google.registry.util.Clock;
import java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.Money;
import org.joda.time.DateTime;
@@ -146,7 +147,7 @@ public class DomainPricingLogicTest {
new FeesAndCredits.Builder()
.setCurrency(USD)
// (13 + 11) * 0.85 == 20.40
.addFeeOrCredit(Fee.create(Money.of(USD, 20.4).getAmount(), CREATE, false))
.addFeeOrCredit(Fee.create(new BigDecimal("20.40"), CREATE, false))
.build());
}
@@ -159,7 +160,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -172,7 +173,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, false))
.build());
}
@@ -185,7 +186,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
@@ -198,7 +199,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 500).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("500.00"), RENEW, true))
.build());
}
@@ -215,7 +216,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
@@ -241,7 +242,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, true))
.build());
}
@@ -281,7 +282,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 500).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("500.00"), RENEW, true))
.build());
}
@@ -308,7 +309,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 400).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("400.00"), RENEW, true))
.build());
}
@@ -350,7 +351,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -376,7 +377,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
.build());
}
@@ -394,7 +395,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, false))
.build());
}
@@ -421,7 +422,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 40).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("40.00"), RENEW, false))
.build());
}
@@ -439,7 +440,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -466,7 +467,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
.build());
}
@@ -484,7 +485,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, false))
.build());
}
@@ -512,7 +513,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 40).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("40.00"), RENEW, false))
.build());
}
@@ -530,7 +531,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -548,7 +549,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("50.00"), RENEW, false))
.build());
}
@@ -567,7 +568,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.00"), RENEW, false))
.build());
}
@@ -597,7 +598,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.00"), RENEW, false))
.build());
}
@@ -628,7 +629,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.00"), RENEW, false))
.build());
assertThat(
Iterables.getLast(DatabaseHelper.loadAllOf(BillingRecurrence.class))
@@ -651,7 +652,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
.build());
}
@@ -679,7 +680,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
.build());
}
@@ -698,7 +699,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 17).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("17.00"), RENEW, false))
.build());
}
@@ -717,7 +718,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 85).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("85.00"), RENEW, false))
.build());
}
@@ -739,7 +740,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -750,7 +751,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
@@ -765,7 +766,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -780,7 +781,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
@@ -796,7 +797,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -812,7 +813,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("10.00"), RENEW, false))
.build());
}
@@ -829,7 +830,7 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1.23).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.23"), RENEW, false))
.build());
}
@@ -846,7 +847,61 @@ public class DomainPricingLogicTest {
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1.23).getAmount(), RENEW, false))
.addFeeOrCredit(Fee.create(new BigDecimal("1.23"), RENEW, false))
.build());
}
@Test
void testGetDomainCreatePrice_nonPremiumCreate_unaffectedRenewal() throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("premium.example")
.setRegistrationBehavior(AllocationToken.RegistrationBehavior.NONPREMIUM_CREATE)
.build());
assertThat(
domainPricingLogic.getCreatePrice(
tld,
"premium.example",
clock.nowUtc(),
1,
false,
false,
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(new BigDecimal("13.00"), CREATE, false))
.build());
// Two-year create should be 13 (standard price) + 100 (premium price)
assertThat(
domainPricingLogic.getCreatePrice(
tld,
"premium.example",
clock.nowUtc(),
2,
false,
false,
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(new BigDecimal("113.00"), CREATE, false))
.build());
assertThat(
domainPricingLogic.getRenewPrice(
tld,
"premium.example",
clock.nowUtc(),
1,
persistDomainAndSetRecurrence("premium.example", DEFAULT, Optional.empty()),
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
.build());
}
}

View File

@@ -564,6 +564,35 @@ public class AllocationTokenTest extends EntityTestCase {
.build();
}
@Test
void testFailures_badNonpremiumCreate() {
createTld("tld");
AllocationToken validToken =
new AllocationToken.Builder()
.setToken("abc")
.setTokenType(SINGLE_USE)
.setRegistrationBehavior(RegistrationBehavior.NONPREMIUM_CREATE)
.setDomainName("example.tld")
.build();
assertThrows(
IllegalArgumentException.class, () -> validToken.asBuilder().setDomainName(null).build());
assertThrows(
IllegalArgumentException.class,
() -> validToken.asBuilder().setDiscountFraction(0.5).build());
assertThrows(
IllegalArgumentException.class,
() ->
validToken
.asBuilder()
.setAllowedEppActions(
ImmutableSet.of(
CommandName.RENEW,
CommandName.TRANSFER,
CommandName.RESTORE,
CommandName.UPDATE))
.build());
}
private void assertBadInitialTransition(TokenStatus status) {
assertBadTransition(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns:domain="urn:ietf:params:xml:ns:domain-1.0" xmlns="urn:ietf:params:xml:ns:epp-1.0"
xmlns:fee12="urn:ietf:params:xml:ns:fee-0.12"
>
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:creData>
<domain:name>rich.example</domain:name>
<domain:crDate>1999-04-03T22:00:00Z</domain:crDate>
<domain:exDate>2000-04-03T22:00:00Z</domain:exDate>
</domain:creData>
</resData>
<extension>
<fee12:creData>
<fee12:currency>USD</fee12:currency>
<fee12:fee description="create">13.00</fee12:fee>
</fee12:creData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>