mirror of
https://github.com/google/nomulus
synced 2026-01-03 11:45:39 +00:00
Change domain-check fee responses for registrars in tiered promos (#2489)
As requested, for registrars participaing in these tiered pricing promos that wish to receive this type of response, we make the following changes: 1. The non-promotional (i.e. incorrect) price is returned as the standard domain-create fee when running a domain check. 2. The promotional (i.e. correct) price is returned as a special custom command class with a name of "STANDARD PROMO" when running a domain check. 3. Domain creates will return the non-promotional (i.e. incorrect) price rather than the actual promotional price. This is not implemented in this PR.
This commit is contained in:
@@ -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.
|
||||
*
|
||||
* <p>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<String> 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<RegistryConfigSettings> CONFIG_SETTINGS =
|
||||
memoize(RegistryConfig::getConfigSettings);
|
||||
|
||||
|
||||
|
||||
private static InternetAddress parseEmailAddress(String email) {
|
||||
try {
|
||||
return new InternetAddress(email);
|
||||
|
||||
@@ -113,6 +113,7 @@ public class RegistryConfigSettings {
|
||||
public List<String> spec11WebResources;
|
||||
public boolean requireSslCertificates;
|
||||
public double sunriseDomainCreateDiscount;
|
||||
public Set<String> tieredPricingPromotionRegistrarIds;
|
||||
}
|
||||
|
||||
/** Configuration for Hibernate. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,8 @@ registryPolicy:
|
||||
reservedTermsExportDisclaimer: |
|
||||
Disclaimer line 1.
|
||||
Line 2 is this 1.
|
||||
tieredPricingPromotionRegistrarIds:
|
||||
- NewRegistrar
|
||||
|
||||
caching:
|
||||
singletonCacheRefreshSeconds: 0
|
||||
|
||||
@@ -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<String, BillingRecurrence> recurrences = loadRecurrencesForDomains(domainObjs);
|
||||
|
||||
boolean shouldUseTieredPricingPromotion =
|
||||
RegistryConfig.getTieredPricingPromotionRegistrarIds().contains(registrarId);
|
||||
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
|
||||
for (String domainName : getDomainNamesToCheckForFee(feeCheckItem, domainNames.keySet())) {
|
||||
Optional<AllocationToken> 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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -918,24 +918,6 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
|
||||
runFlowAssertResponse(loadFile("domain_check_fee_response_v06.xml"));
|
||||
}
|
||||
|
||||
private void setUpDefaultToken() {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_defaultToken_v06() throws Exception {
|
||||
setUpDefaultToken();
|
||||
@@ -1782,24 +1764,6 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
private void runEapFeeCheckTest(String inputFile, String outputFile) throws Exception {
|
||||
clock.setTo(DateTime.parse("2010-01-01T10:00:00Z"));
|
||||
persistActiveDomain("example1.tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setEapFeeSchedule(
|
||||
new ImmutableSortedMap.Builder<DateTime, Money>(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<DomainCheckFlow, Dom
|
||||
assertTldsFieldLogged("com", "net", "org");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTieredPricingPromoResponse() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
setUpDefaultToken("NewRegistrar");
|
||||
persistActiveDomain("example1.tld");
|
||||
setEppInput("domain_check_fee_v12.xml");
|
||||
runFlowAssertResponse(loadFile("domain_check_tiered_promotion_fee_response_v12.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTieredPricingPromo_registrarNotIncluded_standardResponse() throws Exception {
|
||||
setUpDefaultToken("NewRegistrar");
|
||||
persistActiveDomain("example1.tld");
|
||||
setEppInput("domain_check_fee_v12.xml");
|
||||
runFlowAssertResponse(loadFile("domain_check_fee_response_v12.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTieredPricingPromo_registrarIncluded_noTokenActive() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
persistActiveDomain("example1.tld");
|
||||
|
||||
persistResource(
|
||||
setUpDefaultToken("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setTokenStatusTransitions(
|
||||
ImmutableSortedMap.of(
|
||||
START_OF_TIME,
|
||||
TokenStatus.NOT_STARTED,
|
||||
clock.nowUtc().plusDays(1),
|
||||
TokenStatus.VALID))
|
||||
.build());
|
||||
|
||||
setEppInput("domain_check_fee_v12.xml");
|
||||
runFlowAssertResponse(loadFile("domain_check_fee_response_v12.xml"));
|
||||
}
|
||||
|
||||
private Domain persistPendingDeleteDomain(String domainName) {
|
||||
Domain existingDomain =
|
||||
persistResource(
|
||||
@@ -1867,4 +1868,45 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
|
||||
return persistResource(
|
||||
existingDomain.asBuilder().setAutorenewBillingEvent(renewEvent.createVKey()).build());
|
||||
}
|
||||
|
||||
private void runEapFeeCheckTest(String inputFile, String outputFile) throws Exception {
|
||||
clock.setTo(DateTime.parse("2010-01-01T10:00:00Z"));
|
||||
persistActiveDomain("example1.tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setEapFeeSchedule(
|
||||
new ImmutableSortedMap.Builder<DateTime, Money>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<epp xmlns:domain="urn:ietf:params:xml:ns:domain-1.0" xmlns:contact="urn:ietf:params:xml:ns:contact-1.0" xmlns:fee="urn:ietf:params:xml:ns:fee-0.6" xmlns="urn:ietf:params:xml:ns:epp-1.0" xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0" xmlns:bulkToken="urn:google:params:xml:ns:bulkToken-1.0" xmlns:fee11="urn:ietf:params:xml:ns:fee-0.11" xmlns:fee="urn:ietf:params:xml:ns:fee-0.12" xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1" xmlns:host="urn:ietf:params:xml:ns:host-1.0">
|
||||
<response>
|
||||
<result code="1000">
|
||||
<msg>Command completed successfully</msg>
|
||||
</result>
|
||||
<resData>
|
||||
<domain:chkData>
|
||||
<domain:cd>
|
||||
<domain:name avail="false">example1.tld</domain:name>
|
||||
<domain:reason>In use</domain:reason>
|
||||
</domain:cd>
|
||||
<domain:cd>
|
||||
<domain:name avail="true">example2.tld</domain:name>
|
||||
</domain:cd>
|
||||
<domain:cd>
|
||||
<domain:name avail="true">example3.tld</domain:name>
|
||||
</domain:cd>
|
||||
</domain:chkData>
|
||||
</resData>
|
||||
<extension>
|
||||
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:fee-0.12"
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<fee:currency>USD</fee:currency>
|
||||
<fee:cd>
|
||||
<fee:object>
|
||||
<domain:name>example1.tld</domain:name>
|
||||
</fee:object>
|
||||
<fee:command name="create">
|
||||
<fee:period unit="y">1</fee:period>
|
||||
<fee:fee description="create">13.00</fee:fee>
|
||||
<fee:class>STANDARD</fee:class>
|
||||
</fee:command>
|
||||
</fee:cd>
|
||||
<fee:cd>
|
||||
<fee:object>
|
||||
<domain:name>example1.tld</domain:name>
|
||||
</fee:object>
|
||||
<fee:command name="custom">
|
||||
<fee:period unit="y">1</fee:period>
|
||||
<fee:fee description="create">6.50</fee:fee>
|
||||
<fee:class>STANDARD PROMOTION</fee:class>
|
||||
</fee:command>
|
||||
</fee:cd>
|
||||
<fee:cd>
|
||||
<fee:object>
|
||||
<domain:name>example2.tld</domain:name>
|
||||
</fee:object>
|
||||
<fee:command name="create">
|
||||
<fee:period unit="y">1</fee:period>
|
||||
<fee:fee description="create">13.00</fee:fee>
|
||||
<fee:class>STANDARD</fee:class>
|
||||
</fee:command>
|
||||
</fee:cd>
|
||||
<fee:cd>
|
||||
<fee:object>
|
||||
<domain:name>example2.tld</domain:name>
|
||||
</fee:object>
|
||||
<fee:command name="custom">
|
||||
<fee:period unit="y">1</fee:period>
|
||||
<fee:fee description="create">6.50</fee:fee>
|
||||
<fee:class>STANDARD PROMOTION</fee:class>
|
||||
</fee:command>
|
||||
</fee:cd>
|
||||
<fee:cd>
|
||||
<fee:object>
|
||||
<domain:name>example3.tld</domain:name>
|
||||
</fee:object>
|
||||
<fee:command name="create">
|
||||
<fee:period unit="y">1</fee:period>
|
||||
<fee:fee description="create">13.00</fee:fee>
|
||||
<fee:class>STANDARD</fee:class>
|
||||
</fee:command>
|
||||
</fee:cd>
|
||||
<fee:cd>
|
||||
<fee:object>
|
||||
<domain:name>example3.tld</domain:name>
|
||||
</fee:object>
|
||||
<fee:command name="custom">
|
||||
<fee:period unit="y">1</fee:period>
|
||||
<fee:fee description="create">6.50</fee:fee>
|
||||
<fee:class>STANDARD PROMOTION</fee:class>
|
||||
</fee:command>
|
||||
</fee:cd>
|
||||
</fee:chkData>
|
||||
</extension>
|
||||
<trID>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
<svTRID>server-trid</svTRID>
|
||||
</trID>
|
||||
</response>
|
||||
</epp>
|
||||
Reference in New Issue
Block a user