diff --git a/core/src/main/java/google/registry/config/RegistryConfig.java b/core/src/main/java/google/registry/config/RegistryConfig.java
index 653a9b070..6fd282867 100644
--- a/core/src/main/java/google/registry/config/RegistryConfig.java
+++ b/core/src/main/java/google/registry/config/RegistryConfig.java
@@ -1780,6 +1780,19 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get().registryPolicy.sunriseDomainCreateDiscount;
}
+ /**
+ * List of registrars for which we include a promotional price on domain checks if configured.
+ *
+ *
In these cases, when a default promotion is running for the domain+registrar combination in
+ * question (a DEFAULT_PROMO token is set on the TLD), the standard non-promotional price will be
+ * returned for that domain as the standard create price. We will then add an additional fee check
+ * response with the actual promotional price and a "STANDARD PROMOTION" class.
+ */
+ public static ImmutableSet getTieredPricingPromotionRegistrarIds() {
+ return ImmutableSet.copyOf(
+ CONFIG_SETTINGS.get().registryPolicy.tieredPricingPromotionRegistrarIds);
+ }
+
/**
* Memoizes loading of the {@link RegistryConfigSettings} POJO.
*
@@ -1790,8 +1803,6 @@ public final class RegistryConfig {
public static final Supplier CONFIG_SETTINGS =
memoize(RegistryConfig::getConfigSettings);
-
-
private static InternetAddress parseEmailAddress(String email) {
try {
return new InternetAddress(email);
diff --git a/core/src/main/java/google/registry/config/RegistryConfigSettings.java b/core/src/main/java/google/registry/config/RegistryConfigSettings.java
index 2436f0efc..2385851ab 100644
--- a/core/src/main/java/google/registry/config/RegistryConfigSettings.java
+++ b/core/src/main/java/google/registry/config/RegistryConfigSettings.java
@@ -113,6 +113,7 @@ public class RegistryConfigSettings {
public List spec11WebResources;
public boolean requireSslCertificates;
public double sunriseDomainCreateDiscount;
+ public Set tieredPricingPromotionRegistrarIds;
}
/** 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 592274db0..1c5ddc104 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
@@ -201,6 +201,15 @@ registryPolicy:
# will be free.
sunriseDomainCreateDiscount: 0.15
+ # List of registrars participating in tiered pricing promotions that require
+ # non-standard responses to EPP domain:check and domain:create commands.
+ # When a promotion is active, we will set an additional STANDARD PROMOTION
+ # fee check response on any domain checks that corresponds to the actual
+ # promotional price (the regular response will be the non-promotional price).
+ # In addition, we will return the non-promotional (i.e. incorrect) price on
+ # domain create requests.
+ tieredPricingPromotionRegistrarIds: []
+
hibernate:
# If set to false, calls to tm().transact() cannot be nested. If set to true,
# nested calls to tm().transact() are allowed, as long as they do not specify
diff --git a/core/src/main/java/google/registry/config/files/nomulus-config-unittest.yaml b/core/src/main/java/google/registry/config/files/nomulus-config-unittest.yaml
index 74a05ea89..fb288c3b3 100644
--- a/core/src/main/java/google/registry/config/files/nomulus-config-unittest.yaml
+++ b/core/src/main/java/google/registry/config/files/nomulus-config-unittest.yaml
@@ -9,6 +9,8 @@ registryPolicy:
reservedTermsExportDisclaimer: |
Disclaimer line 1.
Line 2 is this 1.
+ tieredPricingPromotionRegistrarIds:
+ - NewRegistrar
caching:
singletonCacheRefreshSeconds: 0
diff --git a/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java
index 22359959c..933dacded 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java
@@ -43,6 +43,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.net.InternetDomainName;
+import google.registry.config.RegistryConfig;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.EppException;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
@@ -70,6 +71,7 @@ import google.registry.model.domain.DomainCommand.Check;
import google.registry.model.domain.fee.FeeCheckCommandExtension;
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
+import google.registry.model.domain.fee.FeeQueryCommandExtensionItem;
import google.registry.model.domain.fee06.FeeCheckCommandExtensionV06;
import google.registry.model.domain.launch.LaunchCheckExtension;
import google.registry.model.domain.token.AllocationToken;
@@ -129,6 +131,9 @@ import org.joda.time.DateTime;
@ReportingSpec(ActivityReportField.DOMAIN_CHECK)
public final class DomainCheckFlow implements TransactionalFlow {
+ private static final String STANDARD_FEE_RESPONSE_CLASS = "STANDARD";
+ private static final String STANDARD_PROMOTION_FEE_RESPONSE_CLASS = "STANDARD PROMOTION";
+
@Inject ResourceCommand resourceCommand;
@Inject ExtensionManager extensionManager;
@Inject EppInput eppInput;
@@ -300,6 +305,8 @@ public final class DomainCheckFlow implements TransactionalFlow {
loadDomainsForChecks(feeCheck, domainNames, existingDomains);
ImmutableMap recurrences = loadRecurrencesForDomains(domainObjs);
+ boolean shouldUseTieredPricingPromotion =
+ RegistryConfig.getTieredPricingPromotionRegistrarIds().contains(registrarId);
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
for (String domainName : getDomainNamesToCheckForFee(feeCheckItem, domainNames.keySet())) {
Optional defaultToken =
@@ -332,6 +339,44 @@ public final class DomainCheckFlow implements TransactionalFlow {
allocationToken.isPresent() ? allocationToken : defaultToken,
availableDomains.contains(domainName),
recurrences.getOrDefault(domainName, null));
+ // In the case of a registrar that is running a tiered pricing promotion, we issue two
+ // responses for the CREATE fee check command: one (the default response) with the
+ // non-promotional price, and one (an extra STANDARD PROMO response) with the actual
+ // promotional price.
+ if (defaultToken.isPresent()
+ && shouldUseTieredPricingPromotion
+ && feeCheckItem
+ .getCommandName()
+ .equals(FeeQueryCommandExtensionItem.CommandName.CREATE)) {
+ // First, set the promotional (real) price under the STANDARD PROMO class
+ builder
+ .setClass(STANDARD_PROMOTION_FEE_RESPONSE_CLASS)
+ .setCommand(
+ FeeQueryCommandExtensionItem.CommandName.CUSTOM,
+ feeCheckItem.getPhase(),
+ feeCheckItem.getSubphase());
+
+ // Next, get the non-promotional price and set it as the standard response to the CREATE
+ // fee check command
+ FeeCheckResponseExtensionItem.Builder> nonPromotionalBuilder =
+ feeCheckItem.createResponseBuilder();
+ handleFeeRequest(
+ feeCheckItem,
+ nonPromotionalBuilder,
+ domainNames.get(domainName),
+ domain,
+ feeCheck.getCurrency(),
+ now,
+ pricingLogic,
+ allocationToken,
+ availableDomains.contains(domainName),
+ recurrences.getOrDefault(domainName, null));
+ responseItems.add(
+ nonPromotionalBuilder
+ .setClass(STANDARD_FEE_RESPONSE_CLASS)
+ .setDomainNameIfSupported(domainName)
+ .build());
+ }
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
} catch (AllocationTokenInvalidForPremiumNameException
| AllocationTokenNotValidForCommandException
diff --git a/core/src/main/java/google/registry/model/domain/fee/FeeQueryCommandExtensionItem.java b/core/src/main/java/google/registry/model/domain/fee/FeeQueryCommandExtensionItem.java
index 2d98090bd..bbb564d89 100644
--- a/core/src/main/java/google/registry/model/domain/fee/FeeQueryCommandExtensionItem.java
+++ b/core/src/main/java/google/registry/model/domain/fee/FeeQueryCommandExtensionItem.java
@@ -34,19 +34,26 @@ public abstract class FeeQueryCommandExtensionItem extends ImmutableObject {
/** The name of a command that might have an associated fee. */
public enum CommandName {
- UNKNOWN(false),
- CREATE(false),
- RENEW(true),
- TRANSFER(true),
- RESTORE(true),
- UPDATE(false);
+ UNKNOWN(false, false),
+ CREATE(false, true),
+ RENEW(true, true),
+ TRANSFER(true, true),
+ RESTORE(true, true),
+ UPDATE(false, true),
+ /**
+ * We don't accept CUSTOM commands in requests but may issue them in responses. A CUSTOM command
+ * name is permitted in general per RFC 8748 section 3.1.
+ */
+ CUSTOM(false, false);
private final boolean loadDomainForCheck;
+ private final boolean acceptableInputAction;
public static CommandName parseKnownCommand(String string) {
try {
CommandName command = valueOf(string);
- checkArgument(!command.equals(UNKNOWN));
+ checkArgument(
+ command.acceptableInputAction, "Command %s is not an acceptable input action", string);
return command;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
@@ -55,8 +62,9 @@ public abstract class FeeQueryCommandExtensionItem extends ImmutableObject {
}
}
- CommandName(boolean loadDomainForCheck) {
+ CommandName(boolean loadDomainForCheck, boolean acceptableInputAction) {
this.loadDomainForCheck = loadDomainForCheck;
+ this.acceptableInputAction = acceptableInputAction;
}
public boolean shouldLoadDomainForCheck() {
diff --git a/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java
index 01db9f56d..6b626ce4c 100644
--- a/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java
+++ b/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java
@@ -918,24 +918,6 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase(Ordering.natural())
- .put(START_OF_TIME, Money.of(USD, 0))
- .put(clock.nowUtc().minusDays(1), Money.of(USD, 100))
- .put(clock.nowUtc().plusDays(1), Money.of(USD, 50))
- .put(clock.nowUtc().plusDays(2), Money.of(USD, 0))
- .build())
- .build());
- setEppInput(inputFile, ImmutableMap.of("CURRENCY", "USD"));
- runFlowAssertResponse(loadFile(outputFile));
- }
-
@Test
void testSuccess_eapFeeCheck_v06() throws Exception {
runEapFeeCheckTest("domain_check_fee_v06.xml", "domain_check_eap_fee_response_v06.xml");
@@ -1836,6 +1800,43 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase(Ordering.natural())
+ .put(START_OF_TIME, Money.of(USD, 0))
+ .put(clock.nowUtc().minusDays(1), Money.of(USD, 100))
+ .put(clock.nowUtc().plusDays(1), Money.of(USD, 50))
+ .put(clock.nowUtc().plusDays(2), Money.of(USD, 0))
+ .build())
+ .build());
+ setEppInput(inputFile, ImmutableMap.of("CURRENCY", "USD"));
+ runFlowAssertResponse(loadFile(outputFile));
+ }
+
+ private AllocationToken setUpDefaultToken() {
+ return setUpDefaultToken("TheRegistrar");
+ }
+
+ private AllocationToken setUpDefaultToken(String registrarId) {
+ AllocationToken defaultToken =
+ persistResource(
+ new AllocationToken.Builder()
+ .setToken("bbbbb")
+ .setTokenType(DEFAULT_PROMO)
+ .setAllowedRegistrarIds(ImmutableSet.of(registrarId))
+ .setAllowedTlds(ImmutableSet.of("tld"))
+ .setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
+ .setDiscountFraction(0.5)
+ .build());
+ persistResource(
+ Tld.get("tld")
+ .asBuilder()
+ .setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
+ .build());
+ return defaultToken;
+ }
}
diff --git a/core/src/test/resources/google/registry/flows/domain/domain_check_tiered_promotion_fee_response_v12.xml b/core/src/test/resources/google/registry/flows/domain/domain_check_tiered_promotion_fee_response_v12.xml
new file mode 100644
index 000000000..a5df0a6a7
--- /dev/null
+++ b/core/src/test/resources/google/registry/flows/domain/domain_check_tiered_promotion_fee_response_v12.xml
@@ -0,0 +1,91 @@
+
+
+
+ Command completed successfully
+
+
+
+
+ example1.tld
+ In use
+
+
+ example2.tld
+
+
+ example3.tld
+
+
+
+
+
+ USD
+
+
+ example1.tld
+
+
+ 1
+ 13.00
+ STANDARD
+
+
+
+
+ example1.tld
+
+
+ 1
+ 6.50
+ STANDARD PROMOTION
+
+
+
+
+ example2.tld
+
+
+ 1
+ 13.00
+ STANDARD
+
+
+
+
+ example2.tld
+
+
+ 1
+ 6.50
+ STANDARD PROMOTION
+
+
+
+
+ example3.tld
+
+
+ 1
+ 13.00
+ STANDARD
+
+
+
+
+ example3.tld
+
+
+ 1
+ 6.50
+ STANDARD PROMOTION
+
+
+
+
+
+ ABC-12345
+ server-trid
+
+
+
\ No newline at end of file