Add additional return values to PricingEngine interface

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123658519
This commit is contained in:
mcilwain 2016-05-31 10:47:02 -07:00 committed by Ben McIlwain
parent ca585dd0b5
commit 91f6c7006e
21 changed files with 191 additions and 191 deletions

View file

@ -181,7 +181,7 @@ public abstract class BaseDomainCreateFlow<R extends DomainBase, B extends Build
tldState = registry.getTldState(now); tldState = registry.getTldState(now);
checkRegistryStateForTld(tld); checkRegistryStateForTld(tld);
domainLabel = domainName.parts().get(0); domainLabel = domainName.parts().get(0);
createCost = getDomainCreateCost(targetId, now, getClientId(), command.getPeriod().getValue()); createCost = getDomainCreateCost(targetId, now, command.getPeriod().getValue());
// The TLD should always be the parent of the requested domain name. // The TLD should always be the parent of the requested domain name.
isAnchorTenantViaReservation = matchesAnchorTenantReservation( isAnchorTenantViaReservation = matchesAnchorTenantReservation(
domainLabel, tld, command.getAuthInfo().getPw().getValue()); domainLabel, tld, command.getAuthInfo().getPw().getValue());

View file

@ -106,8 +106,7 @@ public class DomainAllocateFlow extends DomainCreateOrAllocateFlow {
.setClientId(getClientId()) .setClientId(getClientId())
// Note that the cost is calculated as of now, i.e. the event time, not the billing time, // Note that the cost is calculated as of now, i.e. the event time, not the billing time,
// which may be some additional days into the future. // which may be some additional days into the future.
.setCost( .setCost(getDomainCreateCost(targetId, now, command.getPeriod().getValue()))
getDomainCreateCost(targetId, now, getClientId(), command.getPeriod().getValue()))
.setPeriodYears(command.getPeriod().getValue()) .setPeriodYears(command.getPeriod().getValue())
.setEventTime(now) .setEventTime(now)
// If there are no nameservers on the domain, then they get the benefit of the sunrush // If there are no nameservers on the domain, then they get the benefit of the sunrush

View file

@ -139,7 +139,7 @@ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow<DomainAppl
@Override @Override
protected void verifyDomainCreateIsAllowed() throws EppException { protected void verifyDomainCreateIsAllowed() throws EppException {
validateFeeChallenge(targetId, getTld(), now, getClientId(), feeCreate, createCost); validateFeeChallenge(targetId, getTld(), now, feeCreate, createCost);
if (tldState == TldState.LANDRUSH && !superuser) { if (tldState == TldState.LANDRUSH && !superuser) {
// Prohibit creating a landrush application in LANDRUSH (but not in SUNRUSH) if there is // Prohibit creating a landrush application in LANDRUSH (but not in SUNRUSH) if there is
// exactly one sunrise application for the same name. // exactly one sunrise application for the same name.

View file

@ -18,7 +18,7 @@ import static google.registry.flows.domain.DomainFlowUtils.getReservationType;
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest; import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
import static google.registry.model.EppResourceUtils.checkResourcesExist; import static google.registry.model.EppResourceUtils.checkResourcesExist;
import static google.registry.model.registry.label.ReservationType.UNRESERVED; import static google.registry.model.registry.label.ReservationType.UNRESERVED;
import static google.registry.pricing.PricingEngineProxy.isPremiumName; import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import static google.registry.util.CollectionUtils.nullToEmpty; import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.DomainNameUtils.getTldFromDomainName;
@ -83,7 +83,7 @@ public class DomainCheckFlow extends BaseDomainCheckFlow {
ReservationType reservationType = getReservationType(domainName); ReservationType reservationType = getReservationType(domainName);
Registry registry = Registry.get(domainName.parent().toString()); Registry registry = Registry.get(domainName.parent().toString());
if (reservationType == UNRESERVED if (reservationType == UNRESERVED
&& isPremiumName(domainName, now, getClientId()) && getPricesForDomainName(domainName.toString(), now).isPremium()
&& registry.getPremiumPriceAckRequired() && registry.getPremiumPriceAckRequired()
&& !nullToEmpty(sessionMetadata.getServiceExtensionUris()).contains( && !nullToEmpty(sessionMetadata.getServiceExtensionUris()).contains(
ServiceExtension.FEE_0_6.getUri())) { ServiceExtension.FEE_0_6.getUri())) {
@ -121,7 +121,7 @@ public class DomainCheckFlow extends BaseDomainCheckFlow {
} }
FeeCheck.Builder builder = new FeeCheck.Builder(); FeeCheck.Builder builder = new FeeCheck.Builder();
handleFeeRequest( handleFeeRequest(
domainCheck, builder, domainName, getTldFromDomainName(domainName), getClientId(), now); domainCheck, builder, domainName, getTldFromDomainName(domainName), now);
feeChecksBuilder.add(builder.setName(domainName).build()); feeChecksBuilder.add(builder.setName(domainName).build());
} }
return ImmutableList.of(FeeCheckResponseExtension.create(feeChecksBuilder.build())); return ImmutableList.of(FeeCheckResponseExtension.create(feeChecksBuilder.build()));

View file

@ -108,7 +108,7 @@ public class DomainCreateFlow extends DomainCreateOrAllocateFlow {
@Override @Override
protected final void verifyDomainCreateIsAllowed() throws EppException { protected final void verifyDomainCreateIsAllowed() throws EppException {
String tld = getTld(); String tld = getTld();
validateFeeChallenge(targetId, tld, now, getClientId(), feeCreate, createCost); validateFeeChallenge(targetId, tld, now, feeCreate, createCost);
if (!superuser) { if (!superuser) {
// Prohibit creating a domain if there is an open application for the same name. // Prohibit creating a domain if there is an open application for the same name.
for (DomainApplication application : loadActiveApplicationsByDomainName(targetId, now)) { for (DomainApplication application : loadActiveApplicationsByDomainName(targetId, now)) {

View file

@ -139,7 +139,7 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
TimeOfYear recurrenceTimeOfYear = TimeOfYear recurrenceTimeOfYear =
checkNotNull(gracePeriod.getRecurringBillingEvent()).get().getRecurrenceTimeOfYear(); checkNotNull(gracePeriod.getRecurringBillingEvent()).get().getRecurrenceTimeOfYear();
DateTime autoRenewTime = recurrenceTimeOfYear.getLastInstanceBeforeOrAt(now); DateTime autoRenewTime = recurrenceTimeOfYear.getLastInstanceBeforeOrAt(now);
cost = getDomainRenewCost(targetId, autoRenewTime, getClientId(), 1); cost = getDomainRenewCost(targetId, autoRenewTime, 1);
} else { } else {
cost = checkNotNull(gracePeriod.getOneTimeBillingEvent()).get().getCost(); cost = checkNotNull(gracePeriod.getOneTimeBillingEvent()).get().getCost();
} }

View file

@ -17,7 +17,6 @@ package google.registry.flows.domain;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.difference;
@ -25,9 +24,7 @@ import static google.registry.flows.EppXmlTransformer.unmarshal;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.Registries.findTldForName; import static google.registry.model.registry.Registries.findTldForName;
import static google.registry.model.registry.label.ReservedList.getReservation; import static google.registry.model.registry.label.ReservedList.getReservation;
import static google.registry.pricing.PricingEngineProxy.getDomainCreateCost; import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.pricing.PricingEngineProxy.isPremiumName;
import static google.registry.tldconfig.idn.IdnLabelValidator.findValidIdnTableForTld; import static google.registry.tldconfig.idn.IdnLabelValidator.findValidIdnTableForTld;
import static google.registry.util.CollectionUtils.nullToEmpty; import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.DateTimeUtils.isAtOrAfter; import static google.registry.util.DateTimeUtils.isAtOrAfter;
@ -82,6 +79,7 @@ import google.registry.model.mark.Mark;
import google.registry.model.mark.ProtectedMark; import google.registry.model.mark.ProtectedMark;
import google.registry.model.mark.Trademark; import google.registry.model.mark.Trademark;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
import google.registry.model.pricing.PricingEngine.DomainPrices;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.model.registry.Registry; import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.Registry.TldState;
@ -286,7 +284,6 @@ public class DomainFlowUtils {
throw new TooManyNameserversException(String.format( throw new TooManyNameserversException(String.format(
"Only %d nameservers are allowed per domain", MAX_NAMESERVERS_PER_DOMAIN)); "Only %d nameservers are allowed per domain", MAX_NAMESERVERS_PER_DOMAIN));
} }
} }
static void validateNoDuplicateContacts(Set<DesignatedContact> contacts) static void validateNoDuplicateContacts(Set<DesignatedContact> contacts)
@ -388,7 +385,7 @@ public class DomainFlowUtils {
*/ */
static void verifyPremiumNameIsNotBlocked( static void verifyPremiumNameIsNotBlocked(
String domainName, DateTime priceTime, String clientIdentifier) throws EppException { String domainName, DateTime priceTime, String clientIdentifier) throws EppException {
if (isPremiumName(domainName, priceTime, clientIdentifier)) { if (getPricesForDomainName(domainName, priceTime).isPremium()) {
// NB: The load of the Registar object is transactionless, which means that it should hit // NB: The load of the Registar object is transactionless, which means that it should hit
// memcache most of the time. // memcache most of the time.
if (Registrar.loadByClientId(clientIdentifier).getBlockPremiumNames()) { if (Registrar.loadByClientId(clientIdentifier).getBlockPremiumNames()) {
@ -563,15 +560,12 @@ public class DomainFlowUtils {
BaseFeeResponse.Builder<?, ?> builder, BaseFeeResponse.Builder<?, ?> builder,
String domainName, String domainName,
String tld, String tld,
String clientIdentifier,
DateTime now) throws EppException { DateTime now) throws EppException {
InternetDomainName domain = InternetDomainName.from(domainName); InternetDomainName domain = InternetDomainName.from(domainName);
FeeCommandDescriptor feeCommand = feeRequest.getCommand(); FeeCommandDescriptor feeCommand = feeRequest.getCommand();
Registry registry = Registry.get(tld); Registry registry = Registry.get(tld);
int years = verifyUnitIsYears(feeRequest.getPeriod()).getValue(); int years = verifyUnitIsYears(feeRequest.getPeriod()).getValue();
boolean isSunrise = registry.getTldState(now).equals(TldState.SUNRISE); boolean isSunrise = registry.getTldState(now).equals(TldState.SUNRISE);
boolean isNameCollisionInSunrise =
isSunrise && getReservationType(domain) == ReservationType.NAME_COLLISION;
if (feeCommand.getPhase() != null || feeCommand.getSubphase() != null) { if (feeCommand.getPhase() != null || feeCommand.getSubphase() != null) {
throw new FeeChecksDontSupportPhasesException(); throw new FeeChecksDontSupportPhasesException();
@ -581,14 +575,13 @@ public class DomainFlowUtils {
throw new CurrencyUnitMismatchException(); throw new CurrencyUnitMismatchException();
} }
DomainPrices prices = getPricesForDomainName(domainName, now);
builder builder
.setCommand(feeCommand) .setCommand(feeCommand)
.setCurrency(registry.getCurrency()) .setCurrency(registry.getCurrency())
.setPeriod(feeRequest.getPeriod()) .setPeriod(feeRequest.getPeriod())
// Choose from four classes: premium, premium-collision, collision, or null (standard case). // Choose from four classes: premium, premium-collision, collision, or null (standard case).
.setClass(emptyToNull(Joiner.on('-').skipNulls().join( .setClass(prices.getFeeClass().orNull());
isPremiumName(domainName, now, clientIdentifier) ? "premium" : null,
isNameCollisionInSunrise ? "collision" : null)));
switch (feeCommand.getCommand()) { switch (feeCommand.getCommand()) {
case UNKNOWN: case UNKNOWN:
@ -598,9 +591,7 @@ public class DomainFlowUtils {
builder.setClass("reserved"); // Override whatever class we've set above. builder.setClass("reserved"); // Override whatever class we've set above.
} else { } else {
builder.setFee( builder.setFee(
Fee.create( Fee.create(prices.getCreateCost().multipliedBy(years).getAmount(), "create"));
getDomainCreateCost(domainName, now, clientIdentifier, years).getAmount(),
"create"));
} }
break; break;
case RESTORE: case RESTORE:
@ -609,17 +600,12 @@ public class DomainFlowUtils {
} }
// Restores have a "renew" and a "restore" fee. // Restores have a "renew" and a "restore" fee.
builder.setFee( builder.setFee(
Fee.create( Fee.create(prices.getRenewCost().multipliedBy(years).getAmount(), "renew"),
getDomainRenewCost(domainName, now, clientIdentifier, years).getAmount(),
"renew"),
Fee.create(registry.getStandardRestoreCost().getAmount(), "restore")); Fee.create(registry.getStandardRestoreCost().getAmount(), "restore"));
break; break;
default: default:
// Anything else (transfer|renew) will have a "renew" fee. // Anything else (transfer|renew) will have a "renew" fee.
builder.setFee( builder.setFee(Fee.create(prices.getRenewCost().multipliedBy(years).getAmount(), "renew"));
Fee.create(
getDomainRenewCost(domainName, now, clientIdentifier, years).getAmount(),
"renew"));
} }
} }
@ -627,14 +613,13 @@ public class DomainFlowUtils {
String domainName, String domainName,
String tld, String tld,
DateTime priceTime, DateTime priceTime,
String clientIdentifier,
final BaseFeeCommand feeCommand, final BaseFeeCommand feeCommand,
Money cost, Money cost,
Money... otherCosts) Money... otherCosts)
throws EppException { throws EppException {
Registry registry = Registry.get(tld); Registry registry = Registry.get(tld);
if (registry.getPremiumPriceAckRequired() if (registry.getPremiumPriceAckRequired()
&& isPremiumName(domainName, priceTime, clientIdentifier) && getPricesForDomainName(domainName, priceTime).isPremium()
&& feeCommand == null) { && feeCommand == null) {
throw new FeesRequiredForPremiumNameException(); throw new FeesRequiredForPremiumNameException();
} }
@ -1021,4 +1006,3 @@ public class DomainFlowUtils {
} }
} }
} }

View file

@ -95,7 +95,7 @@ public class DomainInfoFlow extends BaseDomainInfoFlow<DomainResource, Builder>
if (feeInfo != null) { // Fee check was requested. if (feeInfo != null) { // Fee check was requested.
FeeInfoResponseExtension.Builder builder = new FeeInfoResponseExtension.Builder(); FeeInfoResponseExtension.Builder builder = new FeeInfoResponseExtension.Builder();
handleFeeRequest( handleFeeRequest(
feeInfo, builder, getTargetId(), existingResource.getTld(), getClientId(), now); feeInfo, builder, getTargetId(), existingResource.getTld(), now);
extensions.add(builder.build()); extensions.add(builder.build());
} }
if (!registrationTypes.isEmpty()) { if (!registrationTypes.isEmpty()) {

View file

@ -110,9 +110,9 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
existingResource.getRegistrationExpirationTime().toLocalDate())) { existingResource.getRegistrationExpirationTime().toLocalDate())) {
throw new IncorrectCurrentExpirationDateException(); throw new IncorrectCurrentExpirationDateException();
} }
renewCost = getDomainRenewCost(targetId, now, getClientId(), command.getPeriod().getValue()); renewCost = getDomainRenewCost(targetId, now, command.getPeriod().getValue());
validateFeeChallenge( validateFeeChallenge(
targetId, existingResource.getTld(), now, getClientId(), feeRenew, renewCost); targetId, existingResource.getTld(), now, feeRenew, renewCost);
} }
@Override @Override

View file

@ -103,8 +103,8 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainReso
} }
feeUpdate = eppInput.getSingleExtension(FeeUpdateExtension.class); feeUpdate = eppInput.getSingleExtension(FeeUpdateExtension.class);
restoreCost = Registry.get(tld).getStandardRestoreCost(); restoreCost = Registry.get(tld).getStandardRestoreCost();
renewCost = getDomainRenewCost(targetId, now, getClientId(), 1); renewCost = getDomainRenewCost(targetId, now, 1);
validateFeeChallenge(targetId, tld, now, getClientId(), feeUpdate, restoreCost, renewCost); validateFeeChallenge(targetId, tld, now, feeUpdate, restoreCost, renewCost);
} }
@Override @Override

View file

@ -72,16 +72,14 @@ public class DomainTransferApproveFlow extends
String tld = existingResource.getTld(); String tld = existingResource.getTld();
int extraYears = transferData.getExtendedRegistrationYears(); int extraYears = transferData.getExtendedRegistrationYears();
// Bill for the transfer. // Bill for the transfer.
BillingEvent.OneTime billingEvent = new BillingEvent.OneTime.Builder() BillingEvent.OneTime billingEvent =
new BillingEvent.OneTime.Builder()
.setReason(Reason.TRANSFER) .setReason(Reason.TRANSFER)
.setTargetId(targetId) .setTargetId(targetId)
.setClientId(gainingClientId) .setClientId(gainingClientId)
.setPeriodYears(extraYears) .setPeriodYears(extraYears)
.setCost(getDomainRenewCost( .setCost(
targetId, getDomainRenewCost(targetId, transferData.getTransferRequestTime(), extraYears))
transferData.getTransferRequestTime(),
transferData.getGainingClientId(),
extraYears))
.setEventTime(now) .setEventTime(now)
.setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength())) .setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength()))
.setParent(historyEntry) .setParent(historyEntry)

View file

@ -115,7 +115,7 @@ public class DomainTransferRequestFlow
Registry registry = Registry.get(existingResource.getTld()); Registry registry = Registry.get(existingResource.getTld());
automaticTransferTime = now.plus(registry.getAutomaticTransferLength()); automaticTransferTime = now.plus(registry.getAutomaticTransferLength());
// Note that the gaining registrar is used to calculate the cost of the renewal. // Note that the gaining registrar is used to calculate the cost of the renewal.
renewCost = getDomainRenewCost(targetId, now, getClientId(), command.getPeriod().getValue()); renewCost = getDomainRenewCost(targetId, now, command.getPeriod().getValue());
transferBillingEvent = new BillingEvent.OneTime.Builder() transferBillingEvent = new BillingEvent.OneTime.Builder()
.setReason(Reason.TRANSFER) .setReason(Reason.TRANSFER)
.setTargetId(targetId) .setTargetId(targetId)
@ -156,7 +156,7 @@ public class DomainTransferRequestFlow
verifyPremiumNameIsNotBlocked(targetId, now, getClientId()); verifyPremiumNameIsNotBlocked(targetId, now, getClientId());
} }
validateFeeChallenge( validateFeeChallenge(
targetId, existingResource.getTld(), now, getClientId(), feeTransfer, renewCost); targetId, existingResource.getTld(), now, feeTransfer, renewCost);
checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld()); checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld());
} }

View file

@ -23,12 +23,70 @@ import org.joda.time.DateTime;
public interface PricingEngine { public interface PricingEngine {
/** /**
* Returns the premium price for the given domain name at the given time for the given registrar, * Returns the prices for the given fully qualified domain name at the given time.
* or absent if the domain name isn't premium.
* *
* <p>Note that fullyQualifiedDomainName must not include any subdomains. It should be a single * <p>Note that the fullyQualifiedDomainName must only contain a single part left of the TLD, i.e.
* level above the TLD (which may be multi-part). * subdomains are not allowed, but multi-part TLDs are.
*/ */
public Optional<Money> getPremiumPrice( public DomainPrices getDomainPrices(String fullyQualifiedDomainName, DateTime priceTime);
String fullyQualifiedDomainName, DateTime priceTime, String clientIdentifier);
/**
* A class containing information on prices for a specific domain name.
*
* <p>Any implementation of PricingEngine is responsible for determining all of these.
*/
public static class DomainPrices {
private boolean isPremium;
// TODO(b/26901539): Refactor return values to support an arbitrary list of costs for each of
// create, renew, restore, and transfer.
private Money createCost;
private Money renewCost;
private Optional<Money> oneTimeFee;
private Optional<String> feeClass;
static DomainPrices create(
boolean isPremium,
Money createCost,
Money renewCost,
Optional<Money> oneTimeFee,
Optional<String> feeClass) {
DomainPrices instance = new DomainPrices();
instance.isPremium = isPremium;
instance.createCost = createCost;
instance.renewCost = renewCost;
instance.oneTimeFee = oneTimeFee;
instance.feeClass = feeClass;
return instance;
}
/** Returns whether the domain is premium. */
public boolean isPremium() {
return isPremium;
}
/** Returns the cost to create the domain. */
public Money getCreateCost() {
return createCost;
}
/** Returns the cost to renew the domain. */
public Money getRenewCost() {
return renewCost;
}
/**
* Returns the one time fee to register a domain if there is one.
*
* <p>This is primarily used for EAP registration fees.
*/
public Optional<Money> getOneTimeFee() {
return oneTimeFee;
}
/** Returns the fee class of the cost (used for the Fee extension). */
public Optional<String> getFeeClass() {
return feeClass;
}
}
} }

View file

@ -16,8 +16,13 @@ package google.registry.model.pricing;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static google.registry.model.registry.Registry.TldState.SUNRISE;
import static google.registry.model.registry.label.ReservationType.NAME_COLLISION;
import static google.registry.model.registry.label.ReservedList.getReservation;
import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import com.google.common.base.Joiner;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.net.InternetDomainName; import com.google.common.net.InternetDomainName;
@ -35,19 +40,28 @@ public final class StaticPremiumListPricingEngine implements PricingEngine {
@Inject StaticPremiumListPricingEngine() {} @Inject StaticPremiumListPricingEngine() {}
@Override @Override
public Optional<Money> getPremiumPrice( public DomainPrices getDomainPrices(String fullyQualifiedDomainName, DateTime priceTime) {
String fullyQualifiedDomainName, DateTime priceTime, String clientIdentifier) {
// Note that clientIdentifier and priceTime are not used for determining premium pricing for
// static premium lists.
String tld = getTldFromDomainName(fullyQualifiedDomainName); String tld = getTldFromDomainName(fullyQualifiedDomainName);
String label = InternetDomainName.from(fullyQualifiedDomainName).parts().get(0);
Registry registry = Registry.get(checkNotNull(tld, "tld")); Registry registry = Registry.get(checkNotNull(tld, "tld"));
if (registry.getPremiumList() == null) { Optional<Money> premiumPrice = Optional.<Money>absent();
return Optional.<Money>absent(); if (registry.getPremiumList() != null) {
}
String listName = registry.getPremiumList().getName(); String listName = registry.getPremiumList().getName();
Optional<PremiumList> premiumList = PremiumList.get(listName); Optional<PremiumList> premiumList = PremiumList.get(listName);
checkState(premiumList.isPresent(), "Could not load premium list: %s", listName); checkState(premiumList.isPresent(), "Could not load premium list: %s", listName);
String label = InternetDomainName.from(fullyQualifiedDomainName).parts().get(0); premiumPrice = premiumList.get().getPremiumPrice(label);
return premiumList.get().getPremiumPrice(label); }
boolean isNameCollisionInSunrise =
registry.getTldState(priceTime).equals(SUNRISE)
&& getReservation(label, tld) == NAME_COLLISION;
String feeClass = emptyToNull(Joiner.on('-').skipNulls().join(
premiumPrice.isPresent() ? "premium" : null,
isNameCollisionInSunrise ? "collision" : null));
return DomainPrices.create(
premiumPrice.isPresent(),
premiumPrice.or(registry.getStandardCreateCost()),
premiumPrice.or(registry.getStandardRenewCost(priceTime)),
Optional.<Money>absent(),
Optional.<String>fromNullable(feeClass));
} }
} }

View file

@ -16,16 +16,14 @@ package google.registry.pricing;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.registry.Registries.assertTldExists;
import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.net.InternetDomainName;
import google.registry.model.pricing.PricingEngine; import google.registry.model.pricing.PricingEngine;
import google.registry.model.pricing.PricingEngine.DomainPrices;
import google.registry.model.registry.Registry; import google.registry.model.registry.Registry;
import org.joda.money.Money; import org.joda.money.Money;
@ -54,53 +52,29 @@ public final class PricingEngineProxy {
return pricingEngine.getClass().getCanonicalName(); return pricingEngine.getClass().getCanonicalName();
}}); }});
/** Returns true if the given domain name is on the premium price list. */
public static boolean isPremiumName(
String domainName, DateTime priceTime, String clientIdentifier) {
return isPremiumName(InternetDomainName.from(domainName), priceTime, clientIdentifier);
}
/** Returns true if the given domain name is on the premium price list. */
public static boolean isPremiumName(
InternetDomainName domainName, DateTime priceTime, String clientIdentifier) {
return getPremiumPriceForDomainName(domainName, priceTime, clientIdentifier).isPresent();
}
/** Returns the billing cost for registering the specified domain name for this many years. */ /** Returns the billing cost for registering the specified domain name for this many years. */
public static Money getDomainCreateCost( public static Money getDomainCreateCost(String domainName, DateTime priceTime, int years) {
String domainName, DateTime priceTime, String clientIdentifier, int years) {
checkArgument(years > 0, "Number of years must be positive"); checkArgument(years > 0, "Number of years must be positive");
Optional<Money> annualCost = return getPricesForDomainName(domainName, priceTime).getCreateCost().multipliedBy(years);
getPremiumPriceForDomainName(
InternetDomainName.from(domainName), priceTime, clientIdentifier);
return annualCost
.or(Registry.get(getTldFromDomainName(domainName)).getStandardCreateCost())
.multipliedBy(years);
} }
/** Returns the billing cost for renewing the specified domain name for this many years. */ /** Returns the billing cost for renewing the specified domain name for this many years. */
public static Money getDomainRenewCost( public static Money getDomainRenewCost(String domainName, DateTime priceTime, int years) {
String domainName, DateTime priceTime, String clientIdentifier, int years) {
checkArgument(years > 0, "Number of years must be positive"); checkArgument(years > 0, "Number of years must be positive");
Optional<Money> annualCost = return getPricesForDomainName(domainName, priceTime).getRenewCost().multipliedBy(years);
getPremiumPriceForDomainName(
InternetDomainName.from(domainName), priceTime, clientIdentifier);
return annualCost
.or(Registry.get(getTldFromDomainName(domainName)).getStandardRenewCost(priceTime))
.multipliedBy(years);
} }
/** /**
* Returns whether the given domain name is premium by dispatching to the appropriate * Returns the full {@link DomainPrices} details for the given domain name by dispatching to the
* {@link PricingEngine} based on what is configured for the TLD that the domain is under. * appropriate {@link PricingEngine} based on what is configured for the TLD that the domain is
* under.
*/ */
private static Optional<Money> getPremiumPriceForDomainName( public static DomainPrices getPricesForDomainName(String domainName, DateTime priceTime) {
InternetDomainName domainName, DateTime priceTime, String clientIdentifier) { String tld = getTldFromDomainName(domainName);
String tld = assertTldExists(getTldFromDomainName(domainName));
String clazz = Registry.get(tld).getPricingEngineClassName(); String clazz = Registry.get(tld).getPricingEngineClassName();
PricingEngine engine = pricingEngines.get(clazz); PricingEngine engine = pricingEngines.get(clazz);
checkState(engine != null, "Could not load pricing engine %s for TLD %s", clazz, tld); checkState(engine != null, "Could not load pricing engine %s for TLD %s", clazz, tld);
return engine.getPremiumPrice(domainName.toString(), priceTime, clientIdentifier); return engine.getDomainPrices(domainName, priceTime);
} }
private PricingEngineProxy() {} private PricingEngineProxy() {}

View file

@ -88,9 +88,7 @@ final class CreateAnchorTenantCommand extends MutatingEppToolCommand implements
Money cost = null; Money cost = null;
if (fee) { if (fee) {
cost = cost = getDomainCreateCost(domainName, DateTime.now(UTC), DEFAULT_ANCHOR_TENANT_PERIOD_YEARS);
getDomainCreateCost(
domainName, DateTime.now(UTC), clientIdentifier, DEFAULT_ANCHOR_TENANT_PERIOD_YEARS);
} }
setSoyTemplate(CreateAnchorTenantSoyInfo.getInstance(), setSoyTemplate(CreateAnchorTenantSoyInfo.getInstance(),

View file

@ -43,7 +43,10 @@ public final class DomainNameUtils {
} }
/** /**
* Returns the canonicalized TLD part of a valid domain name by stripping off the leftmost part. * Returns the canonicalized TLD part of a valid fully-qualified domain name by stripping off the
* leftmost part.
*
* <p>This method should not be called for subdomains.
* *
* <p>This function is compatible with multi-part tlds, e.g. {@code co.uk}. This function will * <p>This function is compatible with multi-part tlds, e.g. {@code co.uk}. This function will
* also work on domains for which the registry is not authoritative. If you are certain that the * also work on domains for which the registry is not authoritative. If you are certain that the
@ -51,28 +54,28 @@ public final class DomainNameUtils {
* {@link google.registry.model.registry.Registries#findTldForName(InternetDomainName) * {@link google.registry.model.registry.Registries#findTldForName(InternetDomainName)
* Registries#findTldForName}, which will work on hostnames in addition to domains. * Registries#findTldForName}, which will work on hostnames in addition to domains.
* *
* @param fullyQualifiedDomainName must be a puny-coded domain name (not a subdomain or Unicode) * @param fullyQualifiedDomainName must be a punycode SLD (not a host or unicode)
* @throws IllegalArgumentException if there is no TLD * @throws IllegalArgumentException if there is no TLD
*/ */
public static String getTldFromDomainName(String fullyQualifiedDomainName) { public static String getTldFromDomainName(String fullyQualifiedDomainName) {
checkArgument( checkArgument(
!Strings.isNullOrEmpty(fullyQualifiedDomainName), !Strings.isNullOrEmpty(fullyQualifiedDomainName),
"fullyQualifiedDomainName cannot be null or empty"); "secondLevelDomainName cannot be null or empty");
return getTldFromDomainName(InternetDomainName.from(fullyQualifiedDomainName)); return getTldFromDomainName(InternetDomainName.from(fullyQualifiedDomainName));
} }
/** /**
* Returns the canonicalized TLD part of a valid domain name by stripping off the leftmost part. * Returns the canonicalized TLD part of a valid fully-qualified domain name by stripping off the
* leftmost part.
* *
* <p>This function is compatible with multi-part TLDs and must not be called with subdomains. * <p>This function is compatible with multi-part TLDs and should not be called with subdomains.
* *
* @throws IllegalArgumentException if there is no TLD * @throws IllegalArgumentException if there is no TLD
*/ */
public static String getTldFromDomainName(InternetDomainName fullyQualifiedDomainName) { public static String getTldFromDomainName(InternetDomainName domainName) {
checkArgumentNotNull(fullyQualifiedDomainName); checkArgumentNotNull(domainName);
checkArgument( checkArgument(domainName.hasParent(), "secondLevelDomainName does not have a TLD");
fullyQualifiedDomainName.hasParent(), "fullyQualifiedDomainName does not have a TLD"); return domainName.parent().toString();
return fullyQualifiedDomainName.parent().toString();
} }
private DomainNameUtils() {} private DomainNameUtils() {}

View file

@ -16,7 +16,7 @@ package google.registry.flows.domain;
import static com.google.common.io.BaseEncoding.base16; import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.pricing.PricingEngineProxy.isPremiumName; import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.deleteTld; import static google.registry.testing.DatastoreHelper.deleteTld;
@ -180,7 +180,7 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow,
.setReason(Reason.CREATE) .setReason(Reason.CREATE)
.setTargetId(getUniqueIdFromCommand()) .setTargetId(getUniqueIdFromCommand())
.setClientId("TheRegistrar") .setClientId("TheRegistrar")
.setCost(isPremiumName(getUniqueIdFromCommand(), clock.nowUtc(), "TheRegistrar") .setCost(getPricesForDomainName(getUniqueIdFromCommand(), clock.nowUtc()).isPremium()
? Money.of(USD, 200) ? Money.of(USD, 200)
: Money.of(USD, 26)) : Money.of(USD, 26))
.setPeriodYears(2) .setPeriodYears(2)

View file

@ -180,7 +180,6 @@ public class DomainTransferFlowTestCase<F extends Flow, R extends EppResource>
return createBillingEventForTransfer( return createBillingEventForTransfer(
domain, domain,
historyEntry, historyEntry,
"NewRegistrar",
TRANSFER_REQUEST_TIME, TRANSFER_REQUEST_TIME,
TRANSFER_EXPIRATION_TIME, TRANSFER_EXPIRATION_TIME,
EXTENDED_REGISTRATION_YEARS); EXTENDED_REGISTRATION_YEARS);

View file

@ -17,7 +17,7 @@ package google.registry.pricing;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.pricing.PricingEngineProxy.getDomainCreateCost; import static google.registry.pricing.PricingEngineProxy.getDomainCreateCost;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost; import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.pricing.PricingEngineProxy.isPremiumName; import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.persistPremiumList; import static google.registry.testing.DatastoreHelper.persistPremiumList;
import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistResource;
@ -26,6 +26,7 @@ import static org.joda.money.CurrencyUnit.USD;
import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.pricing.PricingEngine;
import google.registry.model.registry.Registry; import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList; import google.registry.model.registry.label.PremiumList;
import google.registry.testing.AppEngineRule; import google.registry.testing.AppEngineRule;
@ -68,34 +69,34 @@ public class PricingEngineProxyTest {
@Test @Test
public void test_getDomainCreateCost_multipleYears() { public void test_getDomainCreateCost_multipleYears() {
assertThat(getDomainCreateCost("espresso.moka", clock.nowUtc(), "TheRegistrar", 1)) assertThat(getDomainCreateCost("espresso.moka", clock.nowUtc(), 1))
.isEqualTo(Money.parse("USD 13")); .isEqualTo(Money.parse("USD 13"));
assertThat(getDomainCreateCost("espresso.moka", clock.nowUtc(), "TheRegistrar", 5)) assertThat(getDomainCreateCost("espresso.moka", clock.nowUtc(), 5))
.isEqualTo(Money.parse("USD 65")); .isEqualTo(Money.parse("USD 65"));
assertThat(getDomainCreateCost("fraction.moka", clock.nowUtc(), "TheRegistrar", 1)) assertThat(getDomainCreateCost("fraction.moka", clock.nowUtc(), 1))
.isEqualTo(Money.parse("USD 20.50")); .isEqualTo(Money.parse("USD 20.50"));
assertThat(getDomainCreateCost("fraction.moka", clock.nowUtc(), "TheRegistrar", 3)) assertThat(getDomainCreateCost("fraction.moka", clock.nowUtc(), 3))
.isEqualTo(Money.parse("USD 61.50")); .isEqualTo(Money.parse("USD 61.50"));
} }
@Test @Test
public void test_getDomainRenewCost_multipleYears() { public void test_getDomainRenewCost_multipleYears() {
assertThat(getDomainRenewCost("espresso.moka", clock.nowUtc(), "TheRegistrar", 1)) assertThat(getDomainRenewCost("espresso.moka", clock.nowUtc(), 1))
.isEqualTo(Money.parse("USD 11")); .isEqualTo(Money.parse("USD 11"));
assertThat(getDomainRenewCost("espresso.moka", clock.nowUtc(), "TheRegistrar", 5)) assertThat(getDomainRenewCost("espresso.moka", clock.nowUtc(), 5))
.isEqualTo(Money.parse("USD 55")); .isEqualTo(Money.parse("USD 55"));
assertThat(getDomainRenewCost("fraction.moka", clock.nowUtc(), "TheRegistrar", 1)) assertThat(getDomainRenewCost("fraction.moka", clock.nowUtc(), 1))
.isEqualTo(Money.parse("USD 20.50")); .isEqualTo(Money.parse("USD 20.50"));
assertThat(getDomainRenewCost("fraction.moka", clock.nowUtc(), "TheRegistrar", 3)) assertThat(getDomainRenewCost("fraction.moka", clock.nowUtc(), 3))
.isEqualTo(Money.parse("USD 61.50")); .isEqualTo(Money.parse("USD 61.50"));
} }
@Test @Test
public void testIsPremiumDomain() throws Exception { public void testIsPremiumDomain() throws Exception {
createTld("example"); createTld("example");
assertThat(isPremiumName("poor.example", clock.nowUtc(), "TheRegistrar")).isFalse(); assertThat(getPricesForDomainName("poor.example", clock.nowUtc()).isPremium()).isFalse();
assertThat(isPremiumName("rich.example", clock.nowUtc(), "TheRegistrar")).isTrue(); assertThat(getPricesForDomainName("rich.example", clock.nowUtc()).isPremium()).isTrue();
assertThat(isPremiumName("richer.example", clock.nowUtc(), "TheRegistrar")).isTrue(); assertThat(getPricesForDomainName("richer.example", clock.nowUtc()).isPremium()).isTrue();
} }
@Test @Test
@ -103,13 +104,13 @@ public class PricingEngineProxyTest {
// The example tld has a premium price for "rich". // The example tld has a premium price for "rich".
createTld("example"); createTld("example");
// The default value of 17 is set in createTld(). // The default value of 17 is set in createTld().
assertThat(getDomainCreateCost("poor.example", clock.nowUtc(), "TheRegistrar", 1)) assertThat(getDomainCreateCost("poor.example", clock.nowUtc(), 1))
.isEqualTo(Money.of(USD, 13)); .isEqualTo(Money.of(USD, 13));
assertThat(getDomainCreateCost("poor.example", clock.nowUtc(), "TheRegistrar", 2)) assertThat(getDomainCreateCost("poor.example", clock.nowUtc(), 2))
.isEqualTo(Money.of(USD, 26)); .isEqualTo(Money.of(USD, 26));
assertThat(getDomainCreateCost("rich.example", clock.nowUtc(), "TheRegistrar", 1)) assertThat(getDomainCreateCost("rich.example", clock.nowUtc(), 1))
.isEqualTo(Money.of(USD, 100)); .isEqualTo(Money.of(USD, 100));
assertThat(getDomainCreateCost("rich.example", clock.nowUtc(), "TheRegistrar", 2)) assertThat(getDomainCreateCost("rich.example", clock.nowUtc(), 2))
.isEqualTo(Money.of(USD, 200)); .isEqualTo(Money.of(USD, 200));
} }
@ -124,60 +125,36 @@ public class PricingEngineProxyTest {
ImmutableSortedMap.of( ImmutableSortedMap.of(
START_OF_TIME, Money.of(USD, 8), clock.nowUtc(), Money.of(USD, 10))) START_OF_TIME, Money.of(USD, 8), clock.nowUtc(), Money.of(USD, 10)))
.build()); .build());
assertThat(getDomainRenewCost("poor.example", START_OF_TIME, "TheRegistrar", 1)) assertThat(getDomainRenewCost("poor.example", START_OF_TIME, 1))
.isEqualTo(Money.of(USD, 8)); .isEqualTo(Money.of(USD, 8));
assertThat(getDomainRenewCost("poor.example", START_OF_TIME, "TheRegistrar", 2)) assertThat(getDomainRenewCost("poor.example", START_OF_TIME, 2))
.isEqualTo(Money.of(USD, 16)); .isEqualTo(Money.of(USD, 16));
assertThat(getDomainRenewCost("poor.example", clock.nowUtc(), "TheRegistrar", 1)) assertThat(getDomainRenewCost("poor.example", clock.nowUtc(), 1))
.isEqualTo(Money.of(USD, 10)); .isEqualTo(Money.of(USD, 10));
assertThat(getDomainRenewCost("poor.example", clock.nowUtc(), "TheRegistrar", 2)) assertThat(getDomainRenewCost("poor.example", clock.nowUtc(), 2))
.isEqualTo(Money.of(USD, 20)); .isEqualTo(Money.of(USD, 20));
assertThat(getDomainRenewCost("rich.example", START_OF_TIME, "TheRegistrar", 1)) assertThat(getDomainRenewCost("rich.example", START_OF_TIME, 1))
.isEqualTo(Money.of(USD, 100)); .isEqualTo(Money.of(USD, 100));
assertThat(getDomainRenewCost("rich.example", START_OF_TIME, "TheRegistrar", 2)) assertThat(getDomainRenewCost("rich.example", START_OF_TIME, 2))
.isEqualTo(Money.of(USD, 200)); .isEqualTo(Money.of(USD, 200));
assertThat(getDomainRenewCost("rich.example", clock.nowUtc(), "TheRegistrar", 1)) assertThat(getDomainRenewCost("rich.example", clock.nowUtc(), 1))
.isEqualTo(Money.of(USD, 100)); .isEqualTo(Money.of(USD, 100));
assertThat(getDomainRenewCost("rich.example", clock.nowUtc(), "TheRegistrar", 2)) assertThat(getDomainRenewCost("rich.example", clock.nowUtc(), 2))
.isEqualTo(Money.of(USD, 200)); .isEqualTo(Money.of(USD, 200));
} }
@Test @Test
public void testFailure_isPremiumNameForSldNotUnderTld() { public void testFailure_cantLoadPricingEngine() throws Exception {
thrown.expect(IllegalArgumentException.class);
isPremiumName("test.example", clock.nowUtc(), "TheRegistrar");
}
@Test
public void testFailure_isPremiumNameForSldSubdomain() throws Exception {
createTld("example"); createTld("example");
thrown.expect(IllegalArgumentException.class); persistResource(
isPremiumName("rich.sld.example", clock.nowUtc(), "TheRegistrar"); Registry.get("example").asBuilder().setPricingEngineClass(FakePricingEngine.class).build());
thrown.expect(
IllegalStateException.class,
String.format(
"Could not load pricing engine %s for TLD example",
FakePricingEngine.class.getCanonicalName()));
getDomainCreateCost("bad.example", clock.nowUtc(), 1);
} }
@Test private abstract static class FakePricingEngine implements PricingEngine {}
public void testFailure_getCreateCostForSldNotUnderTld() {
thrown.expect(IllegalArgumentException.class);
getDomainCreateCost("test.example", clock.nowUtc(), "TheRegistrar", 1);
}
@Test
public void testFailure_getCreateCostForSldSubdomain() throws Exception {
createTld("example");
thrown.expect(IllegalArgumentException.class);
getDomainCreateCost("rich.sld.example", clock.nowUtc(), "TheRegistrar", 1);
}
@Test
public void testFailure_getRenewCostForSldNotUnderTld() {
thrown.expect(IllegalArgumentException.class);
getDomainRenewCost("test.example", clock.nowUtc(), "TheRegistrar", 1);
}
@Test
public void testFailure_getRenewCostForSldSubdomain() throws Exception {
createTld("example");
thrown.expect(IllegalArgumentException.class);
getDomainRenewCost("rich.sld.example", clock.nowUtc(), "TheRegistrar", 1);
}
} }

View file

@ -428,7 +428,6 @@ public class DatastoreHelper {
public static BillingEvent.OneTime createBillingEventForTransfer( public static BillingEvent.OneTime createBillingEventForTransfer(
DomainResource domain, DomainResource domain,
HistoryEntry historyEntry, HistoryEntry historyEntry,
String gainingClientId,
DateTime costLookupTime, DateTime costLookupTime,
DateTime eventTime, DateTime eventTime,
Integer extendedRegistrationYears) { Integer extendedRegistrationYears) {
@ -440,11 +439,9 @@ public class DatastoreHelper {
eventTime.plus(Registry.get(domain.getTld()).getTransferGracePeriodLength())) eventTime.plus(Registry.get(domain.getTld()).getTransferGracePeriodLength()))
.setClientId("NewRegistrar") .setClientId("NewRegistrar")
.setPeriodYears(extendedRegistrationYears) .setPeriodYears(extendedRegistrationYears)
.setCost(getDomainRenewCost( .setCost(
domain.getFullyQualifiedDomainName(), getDomainRenewCost(
costLookupTime, domain.getFullyQualifiedDomainName(), costLookupTime, extendedRegistrationYears))
gainingClientId,
extendedRegistrationYears))
.setParent(historyEntry) .setParent(historyEntry)
.build(); .build();
} }
@ -505,7 +502,6 @@ public class DatastoreHelper {
BillingEvent.OneTime transferBillingEvent = persistResource(createBillingEventForTransfer( BillingEvent.OneTime transferBillingEvent = persistResource(createBillingEventForTransfer(
domain, domain,
historyEntryDomainTransfer, historyEntryDomainTransfer,
"NewRegistrar",
requestTime, requestTime,
expirationTime, expirationTime,
extendedRegistrationYears)); extendedRegistrationYears));