diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index aeeb096a8..b5392f133 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -14,6 +14,7 @@ package google.registry.flows.domain; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.flows.FlowUtils.persistEntityChanges; import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn; @@ -54,6 +55,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.leapSafeAddYears; +import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.net.InternetDomainName; @@ -80,6 +82,7 @@ import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.billing.BillingEvent.Recurring; +import google.registry.model.billing.BillingEvent.RenewalPriceBehavior; import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainCommand; import google.registry.model.domain.DomainCommand.Create; @@ -115,7 +118,9 @@ import google.registry.model.tld.Registry.TldType; import google.registry.model.tld.label.ReservationType; import google.registry.tmch.LordnTaskUtils; import java.util.Optional; +import javax.annotation.Nullable; import javax.inject.Inject; +import org.joda.money.Money; import org.joda.time.DateTime; import org.joda.time.Duration; @@ -327,7 +332,10 @@ public final class DomainCreateFlow implements TransactionalFlow { now); // Create a new autorenew billing event and poll message starting at the expiration time. BillingEvent.Recurring autorenewBillingEvent = - createAutorenewBillingEvent(domainHistoryKey, registrationExpirationTime); + createAutorenewBillingEvent( + domainHistoryKey, + registrationExpirationTime, + getRenewalPriceInfo(isAnchorTenant, allocationToken, feesAndCredits)); PollMessage.Autorenew autorenewPollMessage = createAutorenewPollMessage(domainHistoryKey, registrationExpirationTime); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); @@ -546,7 +554,9 @@ public final class DomainCreateFlow implements TransactionalFlow { } private Recurring createAutorenewBillingEvent( - Key domainHistoryKey, DateTime registrationExpirationTime) { + Key domainHistoryKey, + DateTime registrationExpirationTime, + RenewalPriceInfo renewalpriceInfo) { return new BillingEvent.Recurring.Builder() .setReason(Reason.RENEW) .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) @@ -555,6 +565,8 @@ public final class DomainCreateFlow implements TransactionalFlow { .setEventTime(registrationExpirationTime) .setRecurrenceEndTime(END_OF_TIME) .setParent(domainHistoryKey) + .setRenewalPriceBehavior(renewalpriceInfo.renewalPriceBehavior()) + .setRenewalPrice(renewalpriceInfo.renewalPrice()) .build(); } @@ -611,6 +623,48 @@ public final class DomainCreateFlow implements TransactionalFlow { } } + /** + * Determines the {@link RenewalPriceBehavior} and the renewal price that needs be stored in the + * {@link Recurring} billing events. + * + *

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, + FeesAndCredits feesAndCredits) { + if (isAnchorTenant) { + if (allocationToken.isPresent()) { + checkArgument( + allocationToken.get().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, feesAndCredits.getCreateCost()); + } else { + return RenewalPriceInfo.create(RenewalPriceBehavior.DEFAULT, null); + } + } + + /** A class to store renewal info used in {@link Recurring} billing events. */ + @AutoValue + public abstract static class RenewalPriceInfo { + static DomainCreateFlow.RenewalPriceInfo create( + RenewalPriceBehavior renewalPriceBehavior, @Nullable Money renewalPrice) { + return new AutoValue_DomainCreateFlow_RenewalPriceInfo(renewalPriceBehavior, renewalPrice); + } + + public abstract RenewalPriceBehavior renewalPriceBehavior(); + + @Nullable + public abstract Money renewalPrice(); + } + private static ImmutableList createResponseExtensions( Optional feeCreate, FeesAndCredits feesAndCredits) { return feeCreate.isPresent() diff --git a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java index 29edeed50..24060d460 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java @@ -21,6 +21,9 @@ import static google.registry.flows.FlowTestCase.UserPrivileges.SUPERUSER; import static google.registry.model.billing.BillingEvent.Flag.ANCHOR_TENANT; import static google.registry.model.billing.BillingEvent.Flag.RESERVED; import static google.registry.model.billing.BillingEvent.Flag.SUNRISE; +import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DEFAULT; +import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM; +import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED; import static google.registry.model.domain.fee.Fee.FEE_EXTENSION_URIS; import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE; @@ -83,6 +86,7 @@ import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.domain.DomainCreateFlow.AnchorTenantCreatePeriodException; import google.registry.flows.domain.DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException; import google.registry.flows.domain.DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException; +import google.registry.flows.domain.DomainCreateFlow.RenewalPriceInfo; import google.registry.flows.domain.DomainCreateFlow.SignedMarksOnlyDuringSunriseException; import google.registry.flows.domain.DomainFlowTmchUtils.FoundMarkExpiredException; import google.registry.flows.domain.DomainFlowTmchUtils.FoundMarkNotYetValidException; @@ -149,9 +153,12 @@ import google.registry.flows.exceptions.ResourceCreateContentionException; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason; +import google.registry.model.billing.BillingEvent.RenewalPriceBehavior; import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainHistory; import google.registry.model.domain.GracePeriod; +import google.registry.model.domain.fee.BaseFee.FeeType; +import google.registry.model.domain.fee.Fee; import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.secdns.DelegationSignerData; @@ -177,6 +184,7 @@ import google.registry.testing.TestOfyAndSql; import google.registry.testing.TestOfyOnly; import java.math.BigDecimal; import java.util.Map; +import java.util.Optional; import javax.annotation.Nullable; import org.joda.money.Money; import org.joda.time.DateTime; @@ -258,13 +266,20 @@ class DomainCreateFlowTest extends ResourceFlowTestCase expectedBillingEvents = @@ -1159,6 +1179,28 @@ class DomainCreateFlowTest extends ResourceFlowTestCase + DomainCreateFlow.getRenewalPriceInfo( + true, + Optional.of( + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .setTokenType(SINGLE_USE) + .setRenewalPriceBehavior(SPECIFIED) + .build())), + new FeesAndCredits.Builder() + .setCurrency(USD) + .addFeeOrCredit(Fee.create(BigDecimal.valueOf(0), FeeType.CREATE, true)) + .build())); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("Renewal price behavior cannot be SPECIFIED for anchor tenant"); + } + + @TestOfyAndSql + void testGetRenewalPriceInfo_withInvalidRenewalPriceBehavior_throwsError() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + DomainCreateFlow.getRenewalPriceInfo( + true, + Optional.of( + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .setTokenType(SINGLE_USE) + .setRenewalPriceBehavior(RenewalPriceBehavior.valueOf("INVALID")) + .build())), + new FeesAndCredits.Builder() + .setCurrency(USD) + .addFeeOrCredit(Fee.create(BigDecimal.valueOf(0), FeeType.CREATE, true)) + .build())); + assertThat(thrown) + .hasMessageThat() + .isEqualTo( + "No enum constant" + + " google.registry.model.billing.BillingEvent.RenewalPriceBehavior.INVALID"); + } }