From a4540a847a4c51540f5db7463edcb50a59f02c49 Mon Sep 17 00:00:00 2001 From: gbrodman Date: Tue, 27 Jun 2023 18:58:44 -0400 Subject: [PATCH] Add configurable discount on sunrise domain creates (#2056) Previously we had a 15% discount applied at invoicing time. We got rid of that inadvertently in 2022 and we want to add it back, but instead of being applied at invoicing time we'll just apply it directly to the creation cost when creating the billing events. Note: previous behavior didn't care about standard vs premium pricing so we don't either https://buganizer.corp.google.com/issues/287070313 is a bug for the issue, and https://github.com/google/nomulus/pull/1710/files#diff-5097b0ef57578718444ea6b9d4c6cb32f655686a37e2ca3dd96ad2db86a77f06L151-L170 is the section of the pull request that inadvertently removed it --- .../registry/config/RegistryConfig.java | 5 + .../config/RegistryConfigSettings.java | 1 + .../registry/config/files/default-config.yaml | 5 + .../flows/domain/DomainCreateFlow.java | 3 +- .../flows/domain/DomainFlowUtils.java | 1 + .../flows/domain/DomainPricingLogic.java | 16 ++- .../flows/domain/DomainCreateFlowTest.java | 18 ++- .../flows/domain/DomainPricingLogicTest.java | 103 +++++++++++------- ...heck_fee_reserved_sunrise_response_v06.xml | 6 +- ...ved_sunrise_response_v06_with_renewals.xml | 6 +- ...e_reserved_sunrise_response_v11_create.xml | 6 +- ...heck_fee_reserved_sunrise_response_v12.xml | 6 +- 12 files changed, 112 insertions(+), 64 deletions(-) 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