diff --git a/core/src/main/java/google/registry/config/RegistryConfig.java b/core/src/main/java/google/registry/config/RegistryConfig.java index cac36f2d3..c71f55e81 100644 --- a/core/src/main/java/google/registry/config/RegistryConfig.java +++ b/core/src/main/java/google/registry/config/RegistryConfig.java @@ -1576,6 +1576,11 @@ public final class RegistryConfig { return Duration.standardDays(CONFIG_SETTINGS.get().registryPolicy.contactAutomaticTransferDays); } + /** A discount for all sunrise domain creates, between 0.0 (no discount) and 1.0 (free). */ + public static double getSunriseDomainCreateDiscount() { + return CONFIG_SETTINGS.get().registryPolicy.sunriseDomainCreateDiscount; + } + /** * Memoizes loading of the {@link RegistryConfigSettings} POJO. * diff --git a/core/src/main/java/google/registry/config/RegistryConfigSettings.java b/core/src/main/java/google/registry/config/RegistryConfigSettings.java index 42af240ba..a02fe78c9 100644 --- a/core/src/main/java/google/registry/config/RegistryConfigSettings.java +++ b/core/src/main/java/google/registry/config/RegistryConfigSettings.java @@ -107,6 +107,7 @@ public class RegistryConfigSettings { public String registryName; public List spec11WebResources; public boolean requireSslCertificates; + public double sunriseDomainCreateDiscount; } /** Configuration for Hibernate. */ diff --git a/core/src/main/java/google/registry/config/files/default-config.yaml b/core/src/main/java/google/registry/config/files/default-config.yaml index 70d5a7d19..347a2c532 100644 --- a/core/src/main/java/google/registry/config/files/default-config.yaml +++ b/core/src/main/java/google/registry/config/files/default-config.yaml @@ -180,6 +180,11 @@ registryPolicy: # should generally be true for production environments, for added security. requireSslCertificates: true + # A fractional discount, if any, to be provided to all sunrise domain creates. + # 0 means no discount will be applied, and 1 means that all sunrise creates + # will be free. + sunriseDomainCreateDiscount: 0.15 + hibernate: # Make 'SERIALIZABLE' the default isolation level to ensure correctness. # diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index 7c89b604f..2c0e99228 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -337,7 +337,8 @@ public final class DomainCreateFlow implements TransactionalFlow { Optional feeCreate = eppInput.getSingleExtension(FeeCreateCommandExtension.class); FeesAndCredits feesAndCredits = - pricingLogic.getCreatePrice(tld, targetId, now, years, isAnchorTenant, allocationToken); + pricingLogic.getCreatePrice( + tld, targetId, now, years, isAnchorTenant, isSunriseCreate, allocationToken); validateFeeChallenge(feeCreate, feesAndCredits, defaultTokenUsed); Optional secDnsCreate = validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java index 1a1e7c486..859f5a0e4 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java @@ -690,6 +690,7 @@ public class DomainFlowUtils { now, years, isAnchorTenant(domainName, allocationToken, Optional.empty()), + isSunrise, allocationToken) .getFees(); } 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 b2a393fdf..9a9f32895 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java +++ b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java @@ -22,6 +22,7 @@ import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.PreconditionsUtils.checkArgumentPresent; import com.google.common.net.InternetDomainName; +import google.registry.config.RegistryConfig; import google.registry.flows.EppException; import google.registry.flows.EppException.CommandUseErrorException; import google.registry.flows.custom.DomainPricingCustomLogic; @@ -72,26 +73,33 @@ public final class DomainPricingLogic { DateTime dateTime, int years, boolean isAnchorTenant, + boolean isSunriseCreate, Optional allocationToken) throws EppException { CurrencyUnit currency = tld.getCurrency(); - BaseFee createFeeOrCredit; + BaseFee createFee; // Domain create cost is always zero for anchor tenants if (isAnchorTenant) { - createFeeOrCredit = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false); + createFee = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false); } else { DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime); Money domainCreateCost = getDomainCreateCostWithDiscount(domainPrices, years, allocationToken); - createFeeOrCredit = + // Apply a sunrise discount if configured and applicable + if (isSunriseCreate) { + domainCreateCost = + domainCreateCost.multipliedBy( + 1.0d - RegistryConfig.getSunriseDomainCreateDiscount(), RoundingMode.HALF_EVEN); + } + createFee = Fee.create(domainCreateCost.getAmount(), FeeType.CREATE, domainPrices.isPremium()); } // Create fees for the cost and the EAP fee, if any. Fee eapFee = tld.getEapFeeFor(dateTime); FeesAndCredits.Builder feesBuilder = - new FeesAndCredits.Builder().setCurrency(currency).addFeeOrCredit(createFeeOrCredit); + new FeesAndCredits.Builder().setCurrency(currency).addFeeOrCredit(createFee); // Don't charge anchor tenants EAP fees. if (!isAnchorTenant && !eapFee.hasZeroCost()) { feesBuilder.addFeeOrCredit(eapFee); diff --git a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java index 402a9622d..5ea0736c6 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java @@ -276,16 +276,24 @@ class DomainCreateFlowTest extends ResourceFlowTestCase transitions = + ImmutableSortedMap.naturalOrder() + .put(START_OF_TIME, TldState.PREDELEGATION) + .put(clock.nowUtc().minusHours(1), TldState.START_DATE_SUNRISE) + .put(clock.nowUtc().plusHours(1), TldState.GENERAL_AVAILABILITY) + .build(); + Tld sunriseTld = createTld("sunrise", transitions); + assertThat( + domainPricingLogic.getCreatePrice( + sunriseTld, "domain.sunrise", clock.nowUtc(), 2, false, true, Optional.empty())) + .isEqualTo( + new FeesAndCredits.Builder() + .setCurrency(USD) + // 13 * 2 * 0.85 == 22.1 + .addFeeOrCredit(Fee.create(Money.of(USD, 22.1).getAmount(), CREATE, false)) + .build()); + } + @Test void testGetDomainRenewPrice_oneYear_standardDomain_noBilling_isStandardPrice() throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, "standard.example", clock.nowUtc(), 1, null, Optional.empty())) + tld, "standard.example", clock.nowUtc(), 1, null, Optional.empty())) .isEqualTo( new FeesAndCredits.Builder() .setCurrency(USD) @@ -151,7 +172,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, "standard.example", clock.nowUtc(), 5, null, Optional.empty())) + tld, "standard.example", clock.nowUtc(), 5, null, Optional.empty())) .isEqualTo( new FeesAndCredits.Builder() .setCurrency(USD) @@ -164,7 +185,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, "premium.example", clock.nowUtc(), 1, null, Optional.empty())) + tld, "premium.example", clock.nowUtc(), 1, null, Optional.empty())) .isEqualTo( new FeesAndCredits.Builder() .setCurrency(USD) @@ -177,7 +198,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, "premium.example", clock.nowUtc(), 5, null, Optional.empty())) + tld, "premium.example", clock.nowUtc(), 5, null, Optional.empty())) .isEqualTo( new FeesAndCredits.Builder() .setCurrency(USD) @@ -189,7 +210,7 @@ public class DomainPricingLogicTest { void testGetDomainRenewPrice_oneYear_premiumDomain_default_isPremiumPrice() throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 1, @@ -215,7 +236,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 1, @@ -244,7 +265,7 @@ public class DomainPricingLogicTest { AllocationTokenInvalidForPremiumNameException.class, () -> domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 1, @@ -256,7 +277,7 @@ public class DomainPricingLogicTest { void testGetDomainRenewPrice_multiYear_premiumDomain_default_isPremiumCost() throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 5, @@ -283,7 +304,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 5, @@ -313,7 +334,7 @@ public class DomainPricingLogicTest { AllocationTokenInvalidForPremiumNameException.class, () -> domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 5, @@ -326,7 +347,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 1, @@ -352,7 +373,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 1, @@ -370,7 +391,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 5, @@ -397,7 +418,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 5, @@ -415,7 +436,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 1, @@ -442,7 +463,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 1, @@ -460,7 +481,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 5, @@ -488,7 +509,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 5, @@ -506,7 +527,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 1, @@ -524,7 +545,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 5, @@ -542,7 +563,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 1, @@ -570,7 +591,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 1, @@ -601,7 +622,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 1, @@ -626,7 +647,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 5, @@ -654,7 +675,7 @@ public class DomainPricingLogicTest { .build()); assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "standard.example", clock.nowUtc(), 5, @@ -673,7 +694,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 1, @@ -692,7 +713,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getRenewPrice( - registry, + tld, "premium.example", clock.nowUtc(), 5, @@ -713,15 +734,14 @@ public class DomainPricingLogicTest { IllegalArgumentException.class, () -> domainPricingLogic.getRenewPrice( - registry, "standard.example", clock.nowUtc(), -1, null, Optional.empty())); + tld, "standard.example", clock.nowUtc(), -1, null, Optional.empty())); assertThat(thrown).hasMessageThat().isEqualTo("Number of years must be positive"); } @Test void testGetDomainTransferPrice_standardDomain_default_noBilling_defaultRenewalPrice() throws EppException { - assertThat( - domainPricingLogic.getTransferPrice(registry, "standard.example", clock.nowUtc(), null)) + assertThat(domainPricingLogic.getTransferPrice(tld, "standard.example", clock.nowUtc(), null)) .isEqualTo( new FeesAndCredits.Builder() .setCurrency(USD) @@ -732,8 +752,7 @@ public class DomainPricingLogicTest { @Test void testGetDomainTransferPrice_premiumDomain_default_noBilling_premiumRenewalPrice() throws EppException { - assertThat( - domainPricingLogic.getTransferPrice(registry, "premium.example", clock.nowUtc(), null)) + assertThat(domainPricingLogic.getTransferPrice(tld, "premium.example", clock.nowUtc(), null)) .isEqualTo( new FeesAndCredits.Builder() .setCurrency(USD) @@ -745,7 +764,7 @@ public class DomainPricingLogicTest { void testGetDomainTransferPrice_standardDomain_default_defaultRenewalPrice() throws EppException { assertThat( domainPricingLogic.getTransferPrice( - registry, + tld, "standard.example", clock.nowUtc(), persistDomainAndSetRecurrence("standard.example", DEFAULT, Optional.empty()))) @@ -760,7 +779,7 @@ public class DomainPricingLogicTest { void testGetDomainTransferPrice_premiumDomain_default_premiumRenewalPrice() throws EppException { assertThat( domainPricingLogic.getTransferPrice( - registry, + tld, "premium.example", clock.nowUtc(), persistDomainAndSetRecurrence("premium.example", DEFAULT, Optional.empty()))) @@ -776,7 +795,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getTransferPrice( - registry, + tld, "standard.example", clock.nowUtc(), persistDomainAndSetRecurrence("standard.example", NONPREMIUM, Optional.empty()))) @@ -792,7 +811,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getTransferPrice( - registry, + tld, "premium.example", clock.nowUtc(), persistDomainAndSetRecurrence("premium.example", NONPREMIUM, Optional.empty()))) @@ -808,7 +827,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getTransferPrice( - registry, + tld, "standard.example", clock.nowUtc(), persistDomainAndSetRecurrence( @@ -825,7 +844,7 @@ public class DomainPricingLogicTest { throws EppException { assertThat( domainPricingLogic.getTransferPrice( - registry, + tld, "premium.example", clock.nowUtc(), persistDomainAndSetRecurrence( diff --git a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v06.xml b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v06.xml index 647175735..02795a4a0 100644 --- a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v06.xml +++ b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v06.xml @@ -55,7 +55,7 @@ USD create 1 - 13.00 + 11.05 allowedinsunrise.tld @@ -83,7 +83,7 @@ USD create 1 - 13.00 + 11.05 collision @@ -115,7 +115,7 @@ USD create 1 - 70.00 + 59.50 premium-collision diff --git a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v06_with_renewals.xml b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v06_with_renewals.xml index b82115747..6372b64e2 100644 --- a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v06_with_renewals.xml +++ b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v06_with_renewals.xml @@ -59,7 +59,7 @@ USD create 1 - 13.00 + 11.05 allowedinsunrise.tld @@ -88,7 +88,7 @@ USD create 1 - 13.00 + 11.05 collision @@ -121,7 +121,7 @@ USD create 1 - 70.00 + 59.50 premium-collision diff --git a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v11_create.xml b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v11_create.xml index 8e387289a..c118fc714 100644 --- a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v11_create.xml +++ b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v11_create.xml @@ -40,7 +40,7 @@ create USD 1 - 13.00 + 11.05 @@ -49,7 +49,7 @@ create USD 1 - 13.00 + 11.05 collision @@ -59,7 +59,7 @@ create USD 1 - 70.00 + 59.50 premium-collision diff --git a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v12.xml b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v12.xml index 94b0d63ff..83ca61e51 100644 --- a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v12.xml +++ b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_reserved_sunrise_response_v12.xml @@ -66,7 +66,7 @@ 1 - 13.00 + 11.05 @@ -102,7 +102,7 @@ 1 - 13.00 + 11.05 collision @@ -142,7 +142,7 @@ 1 - 70.00 + 59.50 premium-collision