Use a potential discount in the AllocationToken when determining domain create price

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=245458027
This commit is contained in:
gbrodman 2019-04-26 11:38:31 -07:00 committed by jianglai
parent 1a1ff94bc5
commit 70c7e6c224
14 changed files with 388 additions and 96 deletions

View file

@ -44,6 +44,7 @@ import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.custom.DomainCheckFlowCustomLogic;
import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseParameters;
import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.domain.token.AllocationTokenDomainCheckResults;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand.Check;
@ -51,6 +52,7 @@ 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.launch.LaunchCheckExtension;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppinput.ResourceCommand;
@ -150,27 +152,38 @@ public final class DomainCheckFlow implements Flow {
Set<String> existingIds = checkResourcesExist(DomainBase.class, targetIds, now);
Optional<AllocationTokenExtension> allocationTokenExtension =
eppInput.getSingleExtension(AllocationTokenExtension.class);
ImmutableMap<InternetDomainName, String> tokenCheckResults =
allocationTokenExtension.isPresent()
? allocationTokenFlowUtils.checkDomainsWithToken(
ImmutableList.copyOf(domainNames.values()),
allocationTokenExtension.get().getAllocationToken(),
clientId,
now)
: ImmutableMap.of();
Optional<AllocationTokenDomainCheckResults> tokenDomainCheckResults =
allocationTokenExtension.map(
tokenExtension ->
allocationTokenFlowUtils.checkDomainsWithToken(
ImmutableList.copyOf(domainNames.values()),
tokenExtension.getAllocationToken(),
clientId,
now));
ImmutableList.Builder<DomainCheck> checks = new ImmutableList.Builder<>();
ImmutableMap<String, TldState> tldStates =
Maps.toMap(seenTlds, tld -> Registry.get(tld).getTldState(now));
ImmutableMap<InternetDomainName, String> domainCheckResults =
tokenDomainCheckResults
.map(AllocationTokenDomainCheckResults::domainCheckResults)
.orElse(ImmutableMap.of());
for (String targetId : targetIds) {
Optional<String> message =
getMessageForCheck(domainNames.get(targetId), existingIds, tokenCheckResults, tldStates);
getMessageForCheck(
domainNames.get(targetId),
existingIds,
domainCheckResults,
tldStates);
checks.add(DomainCheck.create(!message.isPresent(), targetId, message.orElse(null)));
}
Optional<AllocationToken> allocationToken =
tokenDomainCheckResults.flatMap(AllocationTokenDomainCheckResults::token);
BeforeResponseReturnData responseData =
flowCustomLogic.beforeResponse(
BeforeResponseParameters.newBuilder()
.setDomainChecks(checks.build())
.setResponseExtensions(getResponseExtensions(domainNames, now))
.setResponseExtensions(getResponseExtensions(domainNames, now, allocationToken))
.setAsOfDate(now)
.build());
return responseBuilder
@ -199,11 +212,14 @@ public final class DomainCheckFlow implements Flow {
/** Handle the fee check extension. */
private ImmutableList<? extends ResponseExtension> getResponseExtensions(
ImmutableMap<String, InternetDomainName> domainNames, DateTime now) throws EppException {
ImmutableMap<String, InternetDomainName> domainNames,
DateTime now,
Optional<AllocationToken> allocationToken)
throws EppException {
Optional<FeeCheckCommandExtension> feeCheckOpt =
eppInput.getSingleExtension(FeeCheckCommandExtension.class);
if (!feeCheckOpt.isPresent()) {
return ImmutableList.of(); // No fee checks were requested.
return ImmutableList.of(); // No fee checks were requested.
}
FeeCheckCommandExtension<?, ?> feeCheck = feeCheckOpt.get();
ImmutableList.Builder<FeeCheckResponseExtensionItem> responseItems =
@ -217,7 +233,8 @@ public final class DomainCheckFlow implements Flow {
domainNames.get(domainName),
feeCheck.getCurrency(),
now,
pricingLogic);
pricingLogic,
allocationToken);
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
}
}

View file

@ -277,7 +277,8 @@ public class DomainCreateFlow implements TransactionalFlow {
Optional<FeeCreateCommandExtension> feeCreate =
eppInput.getSingleExtension(FeeCreateCommandExtension.class);
FeesAndCredits feesAndCredits =
pricingLogic.getCreatePrice(registry, targetId, now, years, isAnchorTenant);
pricingLogic.getCreatePrice(
registry, targetId, now, years, isAnchorTenant, allocationToken);
validateFeeChallenge(targetId, now, feeCreate, feesAndCredits);
Optional<SecDnsCreateExtension> secDnsCreate =
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
@ -444,7 +445,7 @@ public class DomainCreateFlow implements TransactionalFlow {
eppInput.getSingleExtension(AllocationTokenExtension.class);
return Optional.ofNullable(
extension.isPresent()
? allocationTokenFlowUtils.verifyToken(
? allocationTokenFlowUtils.loadAndVerifyToken(
command, extension.get().getAllocationToken(), registry, clientId, now)
: null);
}

View file

@ -550,7 +550,8 @@ public class DomainFlowUtils {
InternetDomainName domain,
@Nullable CurrencyUnit topLevelCurrency,
DateTime currentDate,
DomainPricingLogic pricingLogic)
DomainPricingLogic pricingLogic,
Optional<AllocationToken> allocationToken)
throws EppException {
DateTime now = currentDate;
// Use the custom effective date specified in the fee check request, if there is one.
@ -588,10 +589,10 @@ public class DomainFlowUtils {
builder.setReasonIfSupported("reserved");
} else {
builder.setAvailIfSupported(true);
// TODO(b/117145844): Once allocation token support for domain check flow is implemented,
// we should be able to calculate the correct price here.
fees =
pricingLogic.getCreatePrice(registry, domainNameString, now, years, false).getFees();
pricingLogic
.getCreatePrice(registry, domainNameString, now, years, false, allocationToken)
.getFees();
}
break;
case RENEW:

View file

@ -163,7 +163,8 @@ public final class DomainInfoFlow implements Flow {
InternetDomainName.from(targetId),
null,
now,
pricingLogic);
pricingLogic,
Optional.empty());
extensions.add(builder.build());
}
return extensions.build();

View file

@ -14,7 +14,7 @@
package google.registry.flows.domain;
import static google.registry.pricing.PricingEngineProxy.getDomainCreateCost;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.pricing.PricingEngineProxy.getDomainFeeClass;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
@ -30,8 +30,12 @@ import google.registry.flows.custom.DomainPricingCustomLogic.UpdatePriceParamete
import google.registry.model.domain.fee.BaseFee;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.pricing.PremiumPricingEngine.DomainPrices;
import google.registry.model.registry.Registry;
import google.registry.pricing.PricingEngineProxy;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.money.CurrencyUnit;
@ -51,16 +55,27 @@ public final class DomainPricingLogic {
@Inject
DomainPricingLogic() {}
/** Returns a new create price for the pricer. */
/**
* Returns a new create price for the pricer.
*
* <p>If {@code allocationToken} is present and the domain is non-premium, that discount will be
* applied to the first year.
*/
public FeesAndCredits getCreatePrice(
Registry registry, String domainName, DateTime date, int years, boolean isAnchorTenant)
Registry registry,
String domainName,
DateTime date,
int years,
boolean isAnchorTenant,
Optional<AllocationToken> allocationToken)
throws EppException {
CurrencyUnit currency = registry.getCurrency();
// Get the vanilla create cost, or 0 for anchor tenants.
BigDecimal domainCreateCost =
isAnchorTenant ? BigDecimal.ZERO : getDomainCreateCost(domainName, date, years).getAmount();
BaseFee createFeeOrCredit = Fee.create(domainCreateCost, FeeType.CREATE);
// Domain create cost is always zero for anchor tenants
Money domainCreateCost =
isAnchorTenant
? Money.of(currency, BigDecimal.ZERO)
: getDomainCreateCostWithDiscount(domainName, date, years, allocationToken);
BaseFee createFeeOrCredit = Fee.create(domainCreateCost.getAmount(), FeeType.CREATE);
// Create fees for the cost and the EAP fee, if any.
Fee eapFee = registry.getEapFeeFor(date);
@ -80,7 +95,6 @@ public final class DomainPricingLogic {
.setAsOfDate(date)
.setYears(years)
.build());
}
/** Returns a new renew price for the pricer. */
@ -154,7 +168,7 @@ public final class DomainPricingLogic {
.setFeesAndCredits(
new FeesAndCredits.Builder()
.setCurrency(currency)
.addFeeOrCredit(feeOrCredit)
.setFeesAndCredits(feeOrCredit)
.build())
.setRegistry(registry)
.setDomainName(InternetDomainName.from(domainName))
@ -166,4 +180,24 @@ public final class DomainPricingLogic {
public Optional<String> getFeeClass(String domainName, DateTime date) {
return getDomainFeeClass(domainName, date);
}
private Money getDomainCreateCostWithDiscount(
String domainName, DateTime date, int years, Optional<AllocationToken> allocationToken) {
DomainPrices domainPrices = PricingEngineProxy.getPricesForDomainName(domainName, date);
checkArgument(
!allocationToken.isPresent()
|| allocationToken.get().getDiscountFraction() == 0.0
|| !domainPrices.isPremium(),
"A nonzero discount code cannot be applied to premium domains");
Money oneYearCreateCost = domainPrices.getCreateCost();
Money totalDomainCreateCost = oneYearCreateCost.multipliedBy(years);
// If a discount is applicable, apply it only to the first year
if (allocationToken.isPresent()) {
Money discount =
oneYearCreateCost.multipliedBy(
allocationToken.get().getDiscountFraction(), RoundingMode.HALF_UP);
totalDomainCreateCost = totalDomainCreateCost.minus(discount);
}
return totalDomainCreateCost;
}
}

View file

@ -14,12 +14,14 @@
package google.registry.flows.domain.token;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.InternetDomainName;
import google.registry.flows.EppException;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.registry.Registry;
import java.util.function.Function;
import org.joda.time.DateTime;
/**
@ -43,11 +45,12 @@ public class AllocationTokenCustomLogic {
/** Performs additional custom logic for performing domain checks using a token. */
public ImmutableMap<InternetDomainName, String> checkDomainsWithToken(
ImmutableMap<InternetDomainName, String> checkResults,
ImmutableList<InternetDomainName> domainNames,
AllocationToken token,
String clientId,
DateTime now) {
// Do nothing.
return checkResults;
return domainNames.stream()
.collect(ImmutableMap.toImmutableMap(Function.identity(), ignored -> ""));
}
}

View file

@ -0,0 +1,36 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.domain.token;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.InternetDomainName;
import google.registry.model.domain.token.AllocationToken;
import java.util.Optional;
/** Value class to represent the result of loading a token and checking domains with it. */
@AutoValue
public abstract class AllocationTokenDomainCheckResults {
public abstract Optional<AllocationToken> token();
public abstract ImmutableMap<InternetDomainName, String> domainCheckResults();
public static AllocationTokenDomainCheckResults create(
Optional<AllocationToken> allocationToken,
ImmutableMap<InternetDomainName, String> domainCheckResults) {
return new AutoValue_AllocationTokenDomainCheckResults(allocationToken, domainCheckResults);
}
}

View file

@ -17,7 +17,9 @@ package google.registry.flows.domain.token;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
@ -29,7 +31,7 @@ import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.registry.Registry;
import google.registry.model.reporting.HistoryEntry;
import java.util.List;
import java.util.function.Function;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.time.DateTime;
@ -49,16 +51,10 @@ public class AllocationTokenFlowUtils {
* @return the loaded {@link AllocationToken} for that string.
* @throws InvalidAllocationTokenException if the token doesn't exist.
*/
public AllocationToken verifyToken(
public AllocationToken loadAndVerifyToken(
DomainCommand.Create command, String token, Registry registry, String clientId, DateTime now)
throws EppException {
AllocationToken tokenEntity = ofy().load().key(Key.create(AllocationToken.class, token)).now();
if (tokenEntity == null) {
throw new InvalidAllocationTokenException();
}
if (tokenEntity.isRedeemed()) {
throw new AlreadyRedeemedAllocationTokenException();
}
AllocationToken tokenEntity = loadToken(token);
return tokenCustomLogic.verifyToken(command, tokenEntity, registry, clientId, now);
}
@ -69,26 +65,21 @@ public class AllocationTokenFlowUtils {
* for a a given domain then it does not validate with this allocation token; domains that do
* validate have blank messages (i.e. no error).
*/
public ImmutableMap<InternetDomainName, String> checkDomainsWithToken(
public AllocationTokenDomainCheckResults checkDomainsWithToken(
List<InternetDomainName> domainNames, String token, String clientId, DateTime now) {
AllocationToken tokenEntity = ofy().load().key(Key.create(AllocationToken.class, token)).now();
String globalResult;
if (tokenEntity == null) {
globalResult = new InvalidAllocationTokenException().getMessage();
} else if (tokenEntity.isRedeemed()) {
globalResult = AlreadyRedeemedAllocationTokenException.ERROR_MSG_SHORT;
} else {
globalResult = "";
try {
AllocationToken tokenEntity = loadToken(token);
// Only call custom logic if there wasn't a global allocation token error that applies to all
// check results. The custom logic can only add errors, not override existing errors.
return AllocationTokenDomainCheckResults.create(
Optional.of(tokenEntity),
tokenCustomLogic.checkDomainsWithToken(
ImmutableList.copyOf(domainNames), tokenEntity, clientId, now));
} catch (EppException e) {
return AllocationTokenDomainCheckResults.create(
Optional.empty(),
ImmutableMap.copyOf(Maps.toMap(domainNames, ignored -> e.getMessage())));
}
ImmutableMap<InternetDomainName, String> checkResults =
domainNames
.stream()
.collect(ImmutableMap.toImmutableMap(Function.identity(), domainName -> globalResult));
// Only call custom logic if there wasn't a global allocation token error that applies to all
// check results. The custom logic can only add errors, not override existing errors.
return globalResult.isEmpty()
? tokenCustomLogic.checkDomainsWithToken(checkResults, tokenEntity, clientId, now)
: checkResults;
}
/**
@ -102,17 +93,22 @@ public class AllocationTokenFlowUtils {
return token.asBuilder().setRedemptionHistoryEntry(redemptionHistoryEntry).build();
}
private AllocationToken loadToken(String token) throws EppException {
AllocationToken tokenEntity = ofy().load().key(Key.create(AllocationToken.class, token)).now();
if (tokenEntity == null) {
throw new InvalidAllocationTokenException();
}
if (tokenEntity.isRedeemed()) {
throw new AlreadyRedeemedAllocationTokenException();
}
return tokenEntity;
}
/** The allocation token was already redeemed. */
public static class AlreadyRedeemedAllocationTokenException
extends AssociationProhibitsOperationException {
public static final String ERROR_MSG_LONG = "The allocation token was already redeemed";
/** A short error message fitting within 32 characters for use in domain check responses. */
public static final String ERROR_MSG_SHORT = "Alloc token was already redeemed";
public AlreadyRedeemedAllocationTokenException() {
super(ERROR_MSG_LONG);
super("Alloc token was already redeemed");
}
}