mirror of
https://github.com/google/nomulus
synced 2025-12-23 06:15:42 +00:00
Map token renewal behavior directly onto BillingRecurrence (#2635)
Instead of using a separate RenewalPriceInfo object, just map the behavior (if it exists) onto the BillingRecurrence with a special carve-out, as always, for anchor tenants (note: this shouldn't matter much since anchor tenants *should* use NONPREMIUM renewal tokens anyway, but just in case, double-check). This also fixes DomainPricingLogic to treat a multiyear create as a one-year-create + n-minus-1-year-renewal for cases where either the creation or the renewal (or both) are nonpremium.
This commit is contained in:
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
package google.registry.flows.domain;
|
package google.registry.flows.domain;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||||
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
||||||
@@ -120,9 +119,7 @@ import google.registry.model.tmch.ClaimsList;
|
|||||||
import google.registry.model.tmch.ClaimsListDao;
|
import google.registry.model.tmch.ClaimsListDao;
|
||||||
import google.registry.tmch.LordnTaskUtils.LordnPhase;
|
import google.registry.tmch.LordnTaskUtils.LordnPhase;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import org.joda.money.Money;
|
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Duration;
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
@@ -363,9 +360,7 @@ public final class DomainCreateFlow implements MutatingFlow {
|
|||||||
// Create a new autorenew billing event and poll message starting at the expiration time.
|
// Create a new autorenew billing event and poll message starting at the expiration time.
|
||||||
BillingRecurrence autorenewBillingEvent =
|
BillingRecurrence autorenewBillingEvent =
|
||||||
createAutorenewBillingEvent(
|
createAutorenewBillingEvent(
|
||||||
domainHistoryId,
|
domainHistoryId, registrationExpirationTime, isAnchorTenant, allocationToken);
|
||||||
registrationExpirationTime,
|
|
||||||
getRenewalPriceInfo(isAnchorTenant, allocationToken));
|
|
||||||
PollMessage.Autorenew autorenewPollMessage =
|
PollMessage.Autorenew autorenewPollMessage =
|
||||||
createAutorenewPollMessage(domainHistoryId, registrationExpirationTime);
|
createAutorenewPollMessage(domainHistoryId, registrationExpirationTime);
|
||||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||||
@@ -625,7 +620,17 @@ public final class DomainCreateFlow implements MutatingFlow {
|
|||||||
private BillingRecurrence createAutorenewBillingEvent(
|
private BillingRecurrence createAutorenewBillingEvent(
|
||||||
HistoryEntryId domainHistoryId,
|
HistoryEntryId domainHistoryId,
|
||||||
DateTime registrationExpirationTime,
|
DateTime registrationExpirationTime,
|
||||||
RenewalPriceInfo renewalpriceInfo) {
|
boolean isAnchorTenant,
|
||||||
|
Optional<AllocationToken> allocationToken) {
|
||||||
|
// Non-standard renewal behaviors can occur for anchor tenants (always NONPREMIUM pricing) or if
|
||||||
|
// explicitly configured in the token (either NONPREMIUM or directly SPECIFIED). Use DEFAULT if
|
||||||
|
// none is configured.
|
||||||
|
RenewalPriceBehavior renewalPriceBehavior =
|
||||||
|
isAnchorTenant
|
||||||
|
? RenewalPriceBehavior.NONPREMIUM
|
||||||
|
: allocationToken
|
||||||
|
.map(AllocationToken::getRenewalPriceBehavior)
|
||||||
|
.orElse(RenewalPriceBehavior.DEFAULT);
|
||||||
return new BillingRecurrence.Builder()
|
return new BillingRecurrence.Builder()
|
||||||
.setReason(Reason.RENEW)
|
.setReason(Reason.RENEW)
|
||||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
@@ -634,8 +639,8 @@ public final class DomainCreateFlow implements MutatingFlow {
|
|||||||
.setEventTime(registrationExpirationTime)
|
.setEventTime(registrationExpirationTime)
|
||||||
.setRecurrenceEndTime(END_OF_TIME)
|
.setRecurrenceEndTime(END_OF_TIME)
|
||||||
.setDomainHistoryId(domainHistoryId)
|
.setDomainHistoryId(domainHistoryId)
|
||||||
.setRenewalPriceBehavior(renewalpriceInfo.renewalPriceBehavior())
|
.setRenewalPriceBehavior(renewalPriceBehavior)
|
||||||
.setRenewalPrice(renewalpriceInfo.renewalPrice())
|
.setRenewalPrice(allocationToken.flatMap(AllocationToken::getRenewalPrice).orElse(null))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,41 +684,6 @@ public final class DomainCreateFlow implements MutatingFlow {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the {@link RenewalPriceBehavior} and the renewal price that needs be stored in the
|
|
||||||
* {@link BillingRecurrence} billing events.
|
|
||||||
*
|
|
||||||
* <p>By default, the renewal price is calculated during the process of renewal. Renewal price
|
|
||||||
* should be the createCost if and only if the renewal price behavior in the {@link
|
|
||||||
* AllocationToken} is 'SPECIFIED'.
|
|
||||||
*/
|
|
||||||
static RenewalPriceInfo getRenewalPriceInfo(
|
|
||||||
boolean isAnchorTenant, Optional<AllocationToken> allocationToken) {
|
|
||||||
if (isAnchorTenant) {
|
|
||||||
allocationToken.ifPresent(
|
|
||||||
token ->
|
|
||||||
checkArgument(
|
|
||||||
token.getRenewalPriceBehavior() != RenewalPriceBehavior.SPECIFIED,
|
|
||||||
"Renewal price behavior cannot be SPECIFIED for anchor tenant"));
|
|
||||||
return RenewalPriceInfo.create(RenewalPriceBehavior.NONPREMIUM, null);
|
|
||||||
} else if (allocationToken.isPresent()
|
|
||||||
&& allocationToken.get().getRenewalPriceBehavior() == RenewalPriceBehavior.SPECIFIED) {
|
|
||||||
return RenewalPriceInfo.create(
|
|
||||||
RenewalPriceBehavior.SPECIFIED, allocationToken.get().getRenewalPrice().get());
|
|
||||||
} else {
|
|
||||||
return RenewalPriceInfo.create(RenewalPriceBehavior.DEFAULT, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A record to store renewal info used in {@link BillingRecurrence} billing events. */
|
|
||||||
public record RenewalPriceInfo(
|
|
||||||
RenewalPriceBehavior renewalPriceBehavior, @Nullable Money renewalPrice) {
|
|
||||||
static RenewalPriceInfo create(
|
|
||||||
RenewalPriceBehavior renewalPriceBehavior, @Nullable Money renewalPrice) {
|
|
||||||
return new RenewalPriceInfo(renewalPriceBehavior, renewalPrice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
|
private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
|
||||||
Optional<FeeCreateCommandExtension> feeCreate, FeesAndCredits feesAndCredits) {
|
Optional<FeeCreateCommandExtension> feeCreate, FeesAndCredits feesAndCredits) {
|
||||||
return feeCreate
|
return feeCreate
|
||||||
|
|||||||
@@ -85,14 +85,10 @@ public final class DomainPricingLogic {
|
|||||||
createFee = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false);
|
createFee = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false);
|
||||||
} else {
|
} else {
|
||||||
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
|
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
|
||||||
if (allocationToken.isPresent()
|
if (allocationToken.isPresent()) {
|
||||||
&& allocationToken
|
// Handle any special NONPREMIUM / SPECIFIED cases configured in the token
|
||||||
.get()
|
|
||||||
.getRegistrationBehavior()
|
|
||||||
.equals(RegistrationBehavior.NONPREMIUM_CREATE)) {
|
|
||||||
domainPrices =
|
domainPrices =
|
||||||
DomainPrices.create(
|
applyTokenToDomainPrices(domainPrices, tld, dateTime, years, allocationToken.get());
|
||||||
false, tld.getCreateBillingCost(dateTime), domainPrices.getRenewCost());
|
|
||||||
}
|
}
|
||||||
Money domainCreateCost =
|
Money domainCreateCost =
|
||||||
getDomainCreateCostWithDiscount(domainPrices, years, allocationToken, tld);
|
getDomainCreateCostWithDiscount(domainPrices, years, allocationToken, tld);
|
||||||
@@ -357,6 +353,27 @@ public final class DomainPricingLogic {
|
|||||||
return totalDomainFlowCost;
|
return totalDomainFlowCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DomainPrices applyTokenToDomainPrices(
|
||||||
|
DomainPrices domainPrices, Tld tld, DateTime dateTime, int years, AllocationToken token) {
|
||||||
|
// Convert to nonpremium iff no premium charges are included (either in create or any renewal)
|
||||||
|
boolean convertToNonPremium =
|
||||||
|
token.getRegistrationBehavior().equals(RegistrationBehavior.NONPREMIUM_CREATE)
|
||||||
|
&& (years == 1
|
||||||
|
|| !token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.DEFAULT));
|
||||||
|
boolean isPremium = domainPrices.isPremium() && !convertToNonPremium;
|
||||||
|
Money createCost =
|
||||||
|
token.getRegistrationBehavior().equals(RegistrationBehavior.NONPREMIUM_CREATE)
|
||||||
|
? tld.getCreateBillingCost(dateTime)
|
||||||
|
: domainPrices.getCreateCost();
|
||||||
|
Money renewCost =
|
||||||
|
token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
? tld.getStandardRenewCost(dateTime)
|
||||||
|
: token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
? token.getRenewalPrice().get()
|
||||||
|
: domainPrices.getRenewCost();
|
||||||
|
return DomainPrices.create(isPremium, createCost, renewCost);
|
||||||
|
}
|
||||||
|
|
||||||
/** An allocation token was provided that is invalid for premium domains. */
|
/** An allocation token was provided that is invalid for premium domains. */
|
||||||
public static class AllocationTokenInvalidForPremiumNameException
|
public static class AllocationTokenInvalidForPremiumNameException
|
||||||
extends CommandUseErrorException {
|
extends CommandUseErrorException {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import static google.registry.flows.FlowTestCase.UserPrivileges.SUPERUSER;
|
|||||||
import static google.registry.model.billing.BillingBase.Flag.ANCHOR_TENANT;
|
import static google.registry.model.billing.BillingBase.Flag.ANCHOR_TENANT;
|
||||||
import static google.registry.model.billing.BillingBase.Flag.RESERVED;
|
import static google.registry.model.billing.BillingBase.Flag.RESERVED;
|
||||||
import static google.registry.model.billing.BillingBase.Flag.SUNRISE;
|
import static google.registry.model.billing.BillingBase.Flag.SUNRISE;
|
||||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEFAULT;
|
|
||||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
|
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
|
||||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
|
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
|
||||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
|
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
|
||||||
@@ -87,7 +86,6 @@ import google.registry.flows.domain.DomainCreateFlow.BulkDomainRegisteredForTooM
|
|||||||
import google.registry.flows.domain.DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException;
|
import google.registry.flows.domain.DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException;
|
||||||
import google.registry.flows.domain.DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException;
|
import google.registry.flows.domain.DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException;
|
||||||
import google.registry.flows.domain.DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException;
|
import google.registry.flows.domain.DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException;
|
||||||
import google.registry.flows.domain.DomainCreateFlow.RenewalPriceInfo;
|
|
||||||
import google.registry.flows.domain.DomainCreateFlow.SignedMarksOnlyDuringSunriseException;
|
import google.registry.flows.domain.DomainCreateFlow.SignedMarksOnlyDuringSunriseException;
|
||||||
import google.registry.flows.domain.DomainFlowTmchUtils.FoundMarkExpiredException;
|
import google.registry.flows.domain.DomainFlowTmchUtils.FoundMarkExpiredException;
|
||||||
import google.registry.flows.domain.DomainFlowTmchUtils.FoundMarkNotYetValidException;
|
import google.registry.flows.domain.DomainFlowTmchUtils.FoundMarkNotYetValidException;
|
||||||
@@ -303,10 +301,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
|||||||
|
|
||||||
boolean isAnchorTenant = expectedBillingFlags.contains(ANCHOR_TENANT);
|
boolean isAnchorTenant = expectedBillingFlags.contains(ANCHOR_TENANT);
|
||||||
// Set up the creation cost.
|
// Set up the creation cost.
|
||||||
BigDecimal createCost =
|
boolean isDomainPremium = isDomainPremium(getUniqueIdFromCommand(), clock.nowUtc());
|
||||||
isDomainPremium(getUniqueIdFromCommand(), clock.nowUtc())
|
BigDecimal createCost = isDomainPremium ? BigDecimal.valueOf(200) : BigDecimal.valueOf(24);
|
||||||
? BigDecimal.valueOf(200)
|
|
||||||
: BigDecimal.valueOf(24);
|
|
||||||
if (isAnchorTenant) {
|
if (isAnchorTenant) {
|
||||||
createCost = BigDecimal.ZERO;
|
createCost = BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
@@ -315,6 +311,26 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
|||||||
createCost.multiply(
|
createCost.multiply(
|
||||||
BigDecimal.valueOf(1 - RegistryConfig.getSunriseDomainCreateDiscount()));
|
BigDecimal.valueOf(1 - RegistryConfig.getSunriseDomainCreateDiscount()));
|
||||||
}
|
}
|
||||||
|
if (allocationToken != null) {
|
||||||
|
if (allocationToken
|
||||||
|
.getRegistrationBehavior()
|
||||||
|
.equals(RegistrationBehavior.NONPREMIUM_CREATE)) {
|
||||||
|
createCost =
|
||||||
|
createCost.subtract(
|
||||||
|
BigDecimal.valueOf(isDomainPremium ? 87 : 0)); // premium is 100, standard 13
|
||||||
|
}
|
||||||
|
if (allocationToken.getRenewalPriceBehavior().equals(NONPREMIUM)) {
|
||||||
|
createCost =
|
||||||
|
createCost.subtract(
|
||||||
|
BigDecimal.valueOf(isDomainPremium ? 89 : 0)); // premium is 100, standard 11
|
||||||
|
}
|
||||||
|
if (allocationToken.getRenewalPriceBehavior().equals(SPECIFIED)) {
|
||||||
|
createCost =
|
||||||
|
createCost
|
||||||
|
.subtract(BigDecimal.valueOf(isDomainPremium ? 100 : 11))
|
||||||
|
.add(allocationToken.getRenewalPrice().get().getAmount());
|
||||||
|
}
|
||||||
|
}
|
||||||
FeesAndCredits feesAndCredits =
|
FeesAndCredits feesAndCredits =
|
||||||
new FeesAndCredits.Builder()
|
new FeesAndCredits.Builder()
|
||||||
.setCurrency(USD)
|
.setCurrency(USD)
|
||||||
@@ -343,8 +359,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
|||||||
.hasType(HistoryEntry.Type.DOMAIN_CREATE)
|
.hasType(HistoryEntry.Type.DOMAIN_CREATE)
|
||||||
.and()
|
.and()
|
||||||
.hasPeriodYears(2);
|
.hasPeriodYears(2);
|
||||||
RenewalPriceInfo renewalPriceInfo =
|
RenewalPriceBehavior expectedRenewalPriceBehavior =
|
||||||
DomainCreateFlow.getRenewalPriceInfo(isAnchorTenant, Optional.ofNullable(allocationToken));
|
isAnchorTenant
|
||||||
|
? RenewalPriceBehavior.NONPREMIUM
|
||||||
|
: Optional.ofNullable(allocationToken)
|
||||||
|
.map(AllocationToken::getRenewalPriceBehavior)
|
||||||
|
.orElse(RenewalPriceBehavior.DEFAULT);
|
||||||
// There should be one bill for the create and one for the recurrence autorenew event.
|
// There should be one bill for the create and one for the recurrence autorenew event.
|
||||||
BillingEvent createBillingEvent =
|
BillingEvent createBillingEvent =
|
||||||
new BillingEvent.Builder()
|
new BillingEvent.Builder()
|
||||||
@@ -369,8 +389,11 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
|||||||
.setEventTime(domain.getRegistrationExpirationTime())
|
.setEventTime(domain.getRegistrationExpirationTime())
|
||||||
.setRecurrenceEndTime(END_OF_TIME)
|
.setRecurrenceEndTime(END_OF_TIME)
|
||||||
.setDomainHistory(historyEntry)
|
.setDomainHistory(historyEntry)
|
||||||
.setRenewalPriceBehavior(renewalPriceInfo.renewalPriceBehavior())
|
.setRenewalPriceBehavior(expectedRenewalPriceBehavior)
|
||||||
.setRenewalPrice(renewalPriceInfo.renewalPrice())
|
.setRenewalPrice(
|
||||||
|
Optional.ofNullable(allocationToken)
|
||||||
|
.flatMap(AllocationToken::getRenewalPrice)
|
||||||
|
.orElse(null))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
ImmutableSet.Builder<BillingBase> expectedBillingEvents =
|
ImmutableSet.Builder<BillingBase> expectedBillingEvents =
|
||||||
@@ -3187,85 +3210,62 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetRenewalPriceInfo_isAnchorTenantWithoutToken_returnsNonPremiumAndNullPrice() {
|
void testSuccess_anchorTenant_nonPremiumRenewal() throws Exception {
|
||||||
assertThat(DomainCreateFlow.getRenewalPriceInfo(true, Optional.empty()))
|
|
||||||
.isEqualTo(RenewalPriceInfo.create(NONPREMIUM, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetRenewalPriceInfo_isAnchorTenantWithDefaultToken_returnsNonPremiumAndNullPrice() {
|
|
||||||
assertThat(DomainCreateFlow.getRenewalPriceInfo(true, Optional.of(allocationToken)))
|
|
||||||
.isEqualTo(RenewalPriceInfo.create(NONPREMIUM, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetRenewalPriceInfo_isNotAnchorTenantWithDefaultToken_returnsDefaultAndNullPrice() {
|
|
||||||
assertThat(DomainCreateFlow.getRenewalPriceInfo(false, Optional.of(allocationToken)))
|
|
||||||
.isEqualTo(RenewalPriceInfo.create(DEFAULT, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetRenewalPriceInfo_isNotAnchorTenantWithoutToken_returnsDefaultAndNullPrice() {
|
|
||||||
assertThat(DomainCreateFlow.getRenewalPriceInfo(false, Optional.empty()))
|
|
||||||
.isEqualTo(RenewalPriceInfo.create(DEFAULT, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void
|
|
||||||
testGetRenewalPriceInfo_isNotAnchorTenantWithSpecifiedInToken_returnsSpecifiedAndCreatePrice() {
|
|
||||||
AllocationToken token =
|
AllocationToken token =
|
||||||
persistResource(
|
persistResource(
|
||||||
new AllocationToken.Builder()
|
new AllocationToken.Builder()
|
||||||
.setToken("abc123")
|
.setToken("abc123")
|
||||||
.setTokenType(SINGLE_USE)
|
.setTokenType(SINGLE_USE)
|
||||||
.setRenewalPriceBehavior(SPECIFIED)
|
.setDomainName("example.tld")
|
||||||
.setRenewalPrice(Money.of(USD, 5))
|
.setRegistrationBehavior(RegistrationBehavior.ANCHOR_TENANT)
|
||||||
.build());
|
.build());
|
||||||
assertThat(DomainCreateFlow.getRenewalPriceInfo(false, Optional.of(token)))
|
persistContactsAndHosts();
|
||||||
.isEqualTo(RenewalPriceInfo.create(SPECIFIED, Money.of(USD, 5)));
|
setEppInput(
|
||||||
|
"domain_create_allocationtoken.xml",
|
||||||
|
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||||
|
runFlow();
|
||||||
|
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetRenewalPriceInfo_isAnchorTenantWithSpecifiedStateInToken_throwsError() {
|
void testSuccess_nonAnchorTenant_nonPremiumRenewal() throws Exception {
|
||||||
IllegalArgumentException thrown =
|
createTld("example");
|
||||||
assertThrows(
|
AllocationToken token =
|
||||||
IllegalArgumentException.class,
|
persistResource(
|
||||||
() ->
|
new AllocationToken.Builder()
|
||||||
DomainCreateFlow.getRenewalPriceInfo(
|
.setToken("abc123")
|
||||||
true,
|
.setTokenType(SINGLE_USE)
|
||||||
Optional.of(
|
.setDomainName("rich.example")
|
||||||
persistResource(
|
.setRenewalPriceBehavior(NONPREMIUM)
|
||||||
new AllocationToken.Builder()
|
.build());
|
||||||
.setToken("abc123")
|
persistContactsAndHosts();
|
||||||
.setTokenType(SINGLE_USE)
|
// Creation is still $100 but it'll create a NONPREMIUM renewal
|
||||||
.setRenewalPriceBehavior(SPECIFIED)
|
setEppInput(
|
||||||
.setRenewalPrice(Money.of(USD, 0))
|
"domain_create_premium_allocationtoken.xml",
|
||||||
.build()))));
|
ImmutableMap.of("YEARS", "2", "FEE", "111.00"));
|
||||||
assertThat(thrown)
|
runFlow();
|
||||||
.hasMessageThat()
|
assertSuccessfulCreate("example", ImmutableSet.of(), token);
|
||||||
.isEqualTo("Renewal price behavior cannot be SPECIFIED for anchor tenant");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetRenewalPriceInfo_withInvalidRenewalPriceBehavior_throwsError() {
|
void testSuccess_specifiedRenewalPriceToken_specifiedRecurrencePrice() throws Exception {
|
||||||
IllegalArgumentException thrown =
|
createTld("example");
|
||||||
assertThrows(
|
AllocationToken token =
|
||||||
IllegalArgumentException.class,
|
persistResource(
|
||||||
() ->
|
new AllocationToken.Builder()
|
||||||
DomainCreateFlow.getRenewalPriceInfo(
|
.setToken("abc123")
|
||||||
true,
|
.setTokenType(SINGLE_USE)
|
||||||
Optional.of(
|
.setDomainName("rich.example")
|
||||||
persistResource(
|
.setRenewalPriceBehavior(SPECIFIED)
|
||||||
new AllocationToken.Builder()
|
.setRenewalPrice(Money.of(USD, 1))
|
||||||
.setToken("abc123")
|
.build());
|
||||||
.setTokenType(SINGLE_USE)
|
persistContactsAndHosts();
|
||||||
.setRenewalPriceBehavior(RenewalPriceBehavior.valueOf("INVALID"))
|
// Creation is still $100 but it'll create a $1 renewal
|
||||||
.build()))));
|
setEppInput(
|
||||||
assertThat(thrown)
|
"domain_create_premium_allocationtoken.xml",
|
||||||
.hasMessageThat()
|
ImmutableMap.of("YEARS", "2", "FEE", "101.00"));
|
||||||
.isEqualTo(
|
runFlow();
|
||||||
"No enum constant"
|
assertSuccessfulCreate("example", ImmutableSet.of(), token);
|
||||||
+ " google.registry.model.billing.BillingBase.RenewalPriceBehavior.INVALID");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1127,7 +1127,7 @@ public class DomainPricingLogicTest {
|
|||||||
.setCurrency(USD)
|
.setCurrency(USD)
|
||||||
.addFeeOrCredit(Fee.create(new BigDecimal("13.00"), CREATE, false))
|
.addFeeOrCredit(Fee.create(new BigDecimal("13.00"), CREATE, false))
|
||||||
.build());
|
.build());
|
||||||
// Two-year create should be 13 (standard price) + 100 (premium price)
|
// Two-year create should be 13 (standard price) + 100 (premium price), and it's premium
|
||||||
assertThat(
|
assertThat(
|
||||||
domainPricingLogic.getCreatePrice(
|
domainPricingLogic.getCreatePrice(
|
||||||
tld,
|
tld,
|
||||||
@@ -1140,7 +1140,7 @@ public class DomainPricingLogicTest {
|
|||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
new FeesAndCredits.Builder()
|
new FeesAndCredits.Builder()
|
||||||
.setCurrency(USD)
|
.setCurrency(USD)
|
||||||
.addFeeOrCredit(Fee.create(new BigDecimal("113.00"), CREATE, false))
|
.addFeeOrCredit(Fee.create(new BigDecimal("113.00"), CREATE, true))
|
||||||
.build());
|
.build());
|
||||||
assertThat(
|
assertThat(
|
||||||
domainPricingLogic.getRenewPrice(
|
domainPricingLogic.getRenewPrice(
|
||||||
@@ -1156,4 +1156,90 @@ public class DomainPricingLogicTest {
|
|||||||
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
|
.addFeeOrCredit(Fee.create(new BigDecimal("100.00"), RENEW, true))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetDomainCreatePrice_premium_multiYear_nonpremiumCreateAndRenewal() throws Exception {
|
||||||
|
AllocationToken allocationToken =
|
||||||
|
persistResource(
|
||||||
|
new AllocationToken.Builder()
|
||||||
|
.setToken("abc123")
|
||||||
|
.setTokenType(SINGLE_USE)
|
||||||
|
.setDomainName("premium.example")
|
||||||
|
.setRegistrationBehavior(AllocationToken.RegistrationBehavior.NONPREMIUM_CREATE)
|
||||||
|
.setRenewalPriceBehavior(NONPREMIUM)
|
||||||
|
.build());
|
||||||
|
// Two-year create should be standard create (13) + renewal (10) because both create and renewal
|
||||||
|
// are standard
|
||||||
|
assertThat(
|
||||||
|
domainPricingLogic.getCreatePrice(
|
||||||
|
tld,
|
||||||
|
"premium.example",
|
||||||
|
clock.nowUtc(),
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Optional.of(allocationToken)))
|
||||||
|
.isEqualTo(
|
||||||
|
new FeesAndCredits.Builder()
|
||||||
|
.setCurrency(USD)
|
||||||
|
.addFeeOrCredit(Fee.create(new BigDecimal("23.00"), CREATE, false))
|
||||||
|
.build());
|
||||||
|
// Similarly, 3 years should be 13 + 10 + 10
|
||||||
|
assertThat(
|
||||||
|
domainPricingLogic.getCreatePrice(
|
||||||
|
tld,
|
||||||
|
"premium.example",
|
||||||
|
clock.nowUtc(),
|
||||||
|
3,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Optional.of(allocationToken)))
|
||||||
|
.isEqualTo(
|
||||||
|
new FeesAndCredits.Builder()
|
||||||
|
.setCurrency(USD)
|
||||||
|
.addFeeOrCredit(Fee.create(new BigDecimal("33.00"), CREATE, false))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetDomainCreatePrice_premium_multiYear_onlyNonpremiumRenewal() throws Exception {
|
||||||
|
AllocationToken allocationToken =
|
||||||
|
persistResource(
|
||||||
|
new AllocationToken.Builder()
|
||||||
|
.setToken("abc123")
|
||||||
|
.setTokenType(SINGLE_USE)
|
||||||
|
.setDomainName("premium.example")
|
||||||
|
.setRenewalPriceBehavior(NONPREMIUM)
|
||||||
|
.build());
|
||||||
|
// Two-year create should be 100 (premium 1st year) plus 10 (nonpremium 2nd year)
|
||||||
|
assertThat(
|
||||||
|
domainPricingLogic.getCreatePrice(
|
||||||
|
tld,
|
||||||
|
"premium.example",
|
||||||
|
clock.nowUtc(),
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Optional.of(allocationToken)))
|
||||||
|
.isEqualTo(
|
||||||
|
new FeesAndCredits.Builder()
|
||||||
|
.setCurrency(USD)
|
||||||
|
.addFeeOrCredit(Fee.create(new BigDecimal("110.00"), CREATE, true))
|
||||||
|
.build());
|
||||||
|
// Similarly, 3 years should be 100 + 10 + 10
|
||||||
|
assertThat(
|
||||||
|
domainPricingLogic.getCreatePrice(
|
||||||
|
tld,
|
||||||
|
"premium.example",
|
||||||
|
clock.nowUtc(),
|
||||||
|
3,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Optional.of(allocationToken)))
|
||||||
|
.isEqualTo(
|
||||||
|
new FeesAndCredits.Builder()
|
||||||
|
.setCurrency(USD)
|
||||||
|
.addFeeOrCredit(Fee.create(new BigDecimal("120.00"), CREATE, true))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user