diff --git a/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java b/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java
index 1574ca109..d75269b6d 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java
@@ -64,6 +64,7 @@ import google.registry.flows.custom.DomainDeleteFlowCustomLogic.BeforeSaveParame
import google.registry.flows.custom.EntityChanges;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
+import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
@@ -260,8 +261,9 @@ public final class DomainDeleteFlow implements TransactionalFlow {
handlePendingTransferOnDelete(existingDomain, newDomain, now, domainHistory);
// Close the autorenew billing event and poll message. This may delete the poll message. Store
// the updated recurring billing event, we'll need it later and can't reload it.
+ Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
BillingEvent.Recurring recurringBillingEvent =
- updateAutorenewRecurrenceEndTime(existingDomain, now);
+ updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
// If there's a pending transfer, the gaining client's autorenew billing
// event and poll message will already have been deleted in
// ResourceDeleteFlow since it's listed in serverApproveEntities.
diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java
index c83f1a133..e9f95582e 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java
@@ -583,7 +583,8 @@ public class DomainFlowUtils {
*
*
Returns the new autorenew recurring billing event.
*/
- public static Recurring updateAutorenewRecurrenceEndTime(Domain domain, DateTime newEndTime) {
+ public static Recurring updateAutorenewRecurrenceEndTime(
+ Domain domain, Recurring existingRecurring, DateTime newEndTime) {
Optional autorenewPollMessage =
tm().loadByKeyIfPresent(domain.getAutorenewPollMessage());
@@ -611,13 +612,9 @@ public class DomainFlowUtils {
tm().put(updatedAutorenewPollMessage);
}
- Recurring recurring =
- tm().loadByKey(domain.getAutorenewBillingEvent())
- .asBuilder()
- .setRecurrenceEndTime(newEndTime)
- .build();
- tm().put(recurring);
- return recurring;
+ Recurring newRecurring = existingRecurring.asBuilder().setRecurrenceEndTime(newEndTime).build();
+ tm().put(newRecurring);
+ return newRecurring;
}
/**
@@ -708,7 +705,10 @@ public class DomainFlowUtils {
throw new TransfersAreAlwaysForOneYearException();
}
builder.setAvailIfSupported(true);
- fees = pricingLogic.getTransferPrice(registry, domainNameString, now).getFees();
+ fees =
+ pricingLogic
+ .getTransferPrice(registry, domainNameString, now, recurringBillingEvent)
+ .getFees();
break;
case UPDATE:
builder.setAvailIfSupported(true);
@@ -767,7 +767,7 @@ public class DomainFlowUtils {
final Optional extends FeeTransformCommandExtension> feeCommand,
FeesAndCredits feesAndCredits)
throws EppException {
- if (isDomainPremium(domainName, priceTime) && !feeCommand.isPresent()) {
+ if (feesAndCredits.hasAnyPremiumFees() && !feeCommand.isPresent()) {
throw new FeesRequiredForPremiumNameException();
}
validateFeesAckedIfPresent(feeCommand, feesAndCredits);
diff --git a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java
index 961717667..bdadfed81 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java
@@ -195,9 +195,14 @@ public final class DomainPricingLogic {
}
/** Returns a new transfer price for the pricer. */
- FeesAndCredits getTransferPrice(Registry registry, String domainName, DateTime dateTime)
+ FeesAndCredits getTransferPrice(
+ Registry registry,
+ String domainName,
+ DateTime dateTime,
+ @Nullable Recurring recurringBillingEvent)
throws EppException {
- DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
+ FeesAndCredits renewPrice =
+ getRenewPrice(registry, domainName, dateTime, 1, recurringBillingEvent);
return customLogic.customizeTransferPrice(
TransferPriceParameters.newBuilder()
.setFeesAndCredits(
@@ -205,9 +210,9 @@ public final class DomainPricingLogic {
.setCurrency(registry.getCurrency())
.addFeeOrCredit(
Fee.create(
- domainPrices.getRenewCost().getAmount(),
+ renewPrice.getRenewCost().getAmount(),
FeeType.RENEW,
- domainPrices.isPremium()))
+ renewPrice.hasAnyPremiumFees()))
.build())
.setRegistry(registry)
.setDomainName(InternetDomainName.from(domainName))
diff --git a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java
index d9b438b1d..bd788f77b 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java
@@ -222,7 +222,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
// End the old autorenew billing event and poll message now. This may delete the poll message.
- updateAutorenewRecurrenceEndTime(existingDomain, now);
+ Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
+ updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
Domain newDomain =
existingDomain
.asBuilder()
diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java
index 65fd9156a..f968077d8 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java
@@ -31,7 +31,6 @@ import static google.registry.model.ResourceTransferUtils.approvePendingTransfer
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.TRANSFER_SUCCESSFUL;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
-import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.util.CollectionUtils.union;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -49,6 +48,7 @@ import google.registry.model.ImmutableObject;
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.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
@@ -96,6 +96,8 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
@Inject @Superuser boolean isSuperuser;
@Inject DomainHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder;
+ @Inject DomainPricingLogic pricingLogic;
+
@Inject DomainTransferApproveFlow() {}
/**
@@ -120,6 +122,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
String gainingRegistrarId = transferData.getGainingRegistrarId();
// Create a transfer billing event for 1 year, unless the superuser extension was used to set
// the transfer period to zero. There is not a transfer cost if the transfer period is zero.
+ Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
Key domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
historyBuilder.setId(domainHistoryKey.getId());
Optional billingEvent =
@@ -131,7 +134,14 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.setTargetId(targetId)
.setRegistrarId(gainingRegistrarId)
.setPeriodYears(1)
- .setCost(getDomainRenewCost(targetId, transferData.getTransferRequestTime(), 1))
+ .setCost(
+ pricingLogic
+ .getTransferPrice(
+ Registry.get(tld),
+ targetId,
+ transferData.getTransferRequestTime(),
+ existingRecurring)
+ .getRenewCost())
.setEventTime(now)
.setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength()))
.setDomainHistoryId(
@@ -161,7 +171,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
}
// Close the old autorenew event and poll message at the transfer time (aka now). This may end
// up deleting the poll message.
- updateAutorenewRecurrenceEndTime(existingDomain, now);
+ updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
DateTime newExpirationTime =
computeExDateForApprovalTime(existingDomain, now, transferData.getTransferPeriod());
// Create a new autorenew event starting at the expiration time.
@@ -172,6 +182,8 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.setTargetId(targetId)
.setRegistrarId(gainingRegistrarId)
.setEventTime(newExpirationTime)
+ .setRenewalPriceBehavior(existingRecurring.getRenewalPriceBehavior())
+ .setRenewalPrice(existingRecurring.getRenewalPrice().orElse(null))
.setRecurrenceEndTime(END_OF_TIME)
.setDomainHistoryId(
new DomainHistoryId(
diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java
index 978c819b2..feeeaa4d3 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java
@@ -40,6 +40,7 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
+import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.metadata.MetadataExtension;
@@ -114,7 +115,8 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
targetId, newDomain.getTransferData(), null, domainHistoryKey));
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
// may recreate the autorenew poll message if it was deleted when the transfer request was made.
- updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME);
+ Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
+ updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, END_OF_TIME);
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java
index 551f2ea3c..b9a2596d4 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java
@@ -42,6 +42,7 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
+import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.metadata.MetadataExtension;
@@ -115,7 +116,8 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
targetId, newDomain.getTransferData(), null, now, domainHistoryKey));
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
// may end up recreating the poll message if it was deleted upon the transfer request.
- updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME);
+ Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
+ updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, END_OF_TIME);
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java
index 0dd65be1c..3cbf510cd 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java
@@ -52,6 +52,7 @@ import google.registry.flows.exceptions.InvalidTransferPeriodValueException;
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException;
+import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Transfer;
import google.registry.model.domain.DomainHistory;
@@ -168,10 +169,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
throw new TransferPeriodZeroAndFeeTransferExtensionException();
}
// If the period is zero, then there is no fee for the transfer.
+ Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
Optional feesAndCredits =
(period.getValue() == 0)
? Optional.empty()
- : Optional.of(pricingLogic.getTransferPrice(registry, targetId, now));
+ : Optional.of(
+ pricingLogic.getTransferPrice(registry, targetId, now, existingRecurring));
if (feesAndCredits.isPresent()) {
validateFeeChallenge(targetId, now, feeTransfer, feesAndCredits.get());
}
@@ -201,6 +204,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
serverApproveNewExpirationTime,
domainHistoryKey,
existingDomain,
+ existingRecurring,
trid,
gainingClientId,
feesAndCredits.map(FeesAndCredits::getTotalCost),
@@ -230,7 +234,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
// the poll message if it has no events left. Note that if the automatic transfer succeeds, then
// cloneProjectedAtTime() will replace these old autorenew entities with the server approve ones
// that we've created in this flow and stored in pendingTransferData.
- updateAutorenewRecurrenceEndTime(existingDomain, automaticTransferTime);
+ updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, automaticTransferTime);
Domain newDomain =
existingDomain
.asBuilder()
diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java b/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java
index 0aff78598..57ffd7f83 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java
@@ -24,6 +24,7 @@ import com.googlecode.objectify.Key;
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.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
@@ -110,6 +111,7 @@ public final class DomainTransferUtils {
DateTime serverApproveNewExpirationTime,
Key domainHistoryKey,
Domain existingDomain,
+ Recurring existingRecurring,
Trid trid,
String gainingRegistrarId,
Optional transferCost,
@@ -144,7 +146,11 @@ public final class DomainTransferUtils {
return builder
.add(
createGainingClientAutorenewEvent(
- serverApproveNewExpirationTime, domainHistoryKey, targetId, gainingRegistrarId))
+ existingRecurring,
+ serverApproveNewExpirationTime,
+ domainHistoryKey,
+ targetId,
+ gainingRegistrarId))
.add(
createGainingClientAutorenewPollMessage(
serverApproveNewExpirationTime, domainHistoryKey, targetId, gainingRegistrarId))
@@ -239,6 +245,7 @@ public final class DomainTransferUtils {
}
private static BillingEvent.Recurring createGainingClientAutorenewEvent(
+ Recurring existingRecurring,
DateTime serverApproveNewExpirationTime,
Key domainHistoryKey,
String targetId,
@@ -250,6 +257,8 @@ public final class DomainTransferUtils {
.setRegistrarId(gainingRegistrarId)
.setEventTime(serverApproveNewExpirationTime)
.setRecurrenceEndTime(END_OF_TIME)
+ .setRenewalPriceBehavior(existingRecurring.getRenewalPriceBehavior())
+ .setRenewalPrice(existingRecurring.getRenewalPrice().orElse(null))
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
diff --git a/core/src/main/java/google/registry/tools/UnrenewDomainCommand.java b/core/src/main/java/google/registry/tools/UnrenewDomainCommand.java
index a71146fde..27ffdaed0 100644
--- a/core/src/main/java/google/registry/tools/UnrenewDomainCommand.java
+++ b/core/src/main/java/google/registry/tools/UnrenewDomainCommand.java
@@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.model.billing.BillingEvent;
+import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.Period;
@@ -215,7 +216,8 @@ class UnrenewDomainCommand extends ConfirmingCommand implements CommandWithRemot
.setHistoryEntry(domainHistory)
.build();
// End the old autorenew billing event and poll message now.
- updateAutorenewRecurrenceEndTime(domain, now);
+ Recurring existingRecurring = tm().loadByKey(domain.getAutorenewBillingEvent());
+ updateAutorenewRecurrenceEndTime(domain, existingRecurring, now);
Domain newDomain =
domain
.asBuilder()
diff --git a/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java b/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java
index 491b93175..b97305c42 100644
--- a/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java
+++ b/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java
@@ -399,4 +399,128 @@ public class DomainPricingLogicTest {
registry, "standard.example", clock.nowUtc(), -1, null));
assertThat(thrown).hasMessageThat().isEqualTo("Number of years must be positive");
}
+
+ @Test
+ void testGetDomainTransferPrice_standardDomain_default_noBilling_defaultRenewalPrice()
+ throws EppException {
+ assertThat(
+ domainPricingLogic.getTransferPrice(registry, "standard.example", clock.nowUtc(), null))
+ .isEqualTo(
+ new FeesAndCredits.Builder()
+ .setCurrency(USD)
+ .addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
+ .build());
+ }
+
+ @Test
+ void testGetDomainTransferPrice_premiumDomain_default_noBilling_premiumRenewalPrice()
+ throws EppException {
+ assertThat(
+ domainPricingLogic.getTransferPrice(registry, "premium.example", clock.nowUtc(), null))
+ .isEqualTo(
+ new FeesAndCredits.Builder()
+ .setCurrency(USD)
+ .addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
+ .build());
+ }
+
+ @Test
+ void testGetDomainTransferPrice_standardDomain_default_defaultRenewalPrice() throws EppException {
+ assertThat(
+ domainPricingLogic.getTransferPrice(
+ registry,
+ "standard.example",
+ clock.nowUtc(),
+ persistDomainAndSetRecurringBillingEvent(
+ "standard.example", DEFAULT, Optional.empty())))
+ .isEqualTo(
+ new FeesAndCredits.Builder()
+ .setCurrency(USD)
+ .addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
+ .build());
+ }
+
+ @Test
+ void testGetDomainTransferPrice_premiumDomain_default_premiumRenewalPrice() throws EppException {
+ assertThat(
+ domainPricingLogic.getTransferPrice(
+ registry,
+ "premium.example",
+ clock.nowUtc(),
+ persistDomainAndSetRecurringBillingEvent(
+ "premium.example", DEFAULT, Optional.empty())))
+ .isEqualTo(
+ new FeesAndCredits.Builder()
+ .setCurrency(USD)
+ .addFeeOrCredit(Fee.create(Money.of(USD, 100).getAmount(), RENEW, true))
+ .build());
+ }
+
+ @Test
+ void testGetDomainTransferPrice_standardDomain_nonPremium_nonPremiumRenewalPrice()
+ throws EppException {
+ assertThat(
+ domainPricingLogic.getTransferPrice(
+ registry,
+ "standard.example",
+ clock.nowUtc(),
+ persistDomainAndSetRecurringBillingEvent(
+ "standard.example", NONPREMIUM, Optional.empty())))
+ .isEqualTo(
+ new FeesAndCredits.Builder()
+ .setCurrency(USD)
+ .addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
+ .build());
+ }
+
+ @Test
+ void testGetDomainTransferPrice_premiumDomain_nonPremium_nonPremiumRenewalPrice()
+ throws EppException {
+ assertThat(
+ domainPricingLogic.getTransferPrice(
+ registry,
+ "premium.example",
+ clock.nowUtc(),
+ persistDomainAndSetRecurringBillingEvent(
+ "premium.example", NONPREMIUM, Optional.empty())))
+ .isEqualTo(
+ new FeesAndCredits.Builder()
+ .setCurrency(USD)
+ .addFeeOrCredit(Fee.create(Money.of(USD, 10).getAmount(), RENEW, false))
+ .build());
+ }
+
+ @Test
+ void testGetDomainTransferPrice_standardDomain_specified_specifiedRenewalPrice()
+ throws EppException {
+ assertThat(
+ domainPricingLogic.getTransferPrice(
+ registry,
+ "standard.example",
+ clock.nowUtc(),
+ persistDomainAndSetRecurringBillingEvent(
+ "standard.example", SPECIFIED, Optional.of(Money.of(USD, 1.23)))))
+ .isEqualTo(
+ new FeesAndCredits.Builder()
+ .setCurrency(USD)
+ .addFeeOrCredit(Fee.create(Money.of(USD, 1.23).getAmount(), RENEW, false))
+ .build());
+ }
+
+ @Test
+ void testGetDomainTransferPrice_premiumDomain_specified_specifiedRenewalPrice()
+ throws EppException {
+ assertThat(
+ domainPricingLogic.getTransferPrice(
+ registry,
+ "premium.example",
+ clock.nowUtc(),
+ persistDomainAndSetRecurringBillingEvent(
+ "premium.example", SPECIFIED, Optional.of(Money.of(USD, 1.23)))))
+ .isEqualTo(
+ new FeesAndCredits.Builder()
+ .setCurrency(USD)
+ .addFeeOrCredit(Fee.create(Money.of(USD, 1.23).getAmount(), RENEW, false))
+ .build());
+ }
}
diff --git a/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java
index f5254e0ea..5bb01f963 100644
--- a/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java
+++ b/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java
@@ -27,6 +27,7 @@ import static google.registry.testing.DatabaseHelper.deleteTestDomain;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
import static google.registry.testing.DatabaseHelper.getPollMessages;
+import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -53,6 +54,7 @@ import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.OneTime;
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.contact.ContactAuthInfo;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
@@ -69,10 +71,13 @@ import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Registry;
+import google.registry.model.tld.label.PremiumList;
+import google.registry.model.tld.label.PremiumListDao;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
+import java.math.BigDecimal;
import java.util.Arrays;
import java.util.stream.Stream;
import org.joda.money.Money;
@@ -415,6 +420,103 @@ class DomainTransferApproveFlowTest
.setRecurringEventKey(domain.getAutorenewBillingEvent()));
}
+ @Test
+ void testSuccess_nonpremiumPriceRenewalBehavior_carriesOver() throws Exception {
+ PremiumList pl =
+ PremiumListDao.save(
+ new PremiumList.Builder()
+ .setCurrency(USD)
+ .setName("tld")
+ .setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
+ .build());
+ persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
+ setupDomainWithPendingTransfer("example", "tld");
+ domain = loadByEntity(domain);
+ persistResource(
+ loadByKey(domain.getAutorenewBillingEvent())
+ .asBuilder()
+ .setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
+ .build());
+ setEppInput("domain_transfer_approve_wildcard.xml", ImmutableMap.of("DOMAIN", "example.tld"));
+ DateTime now = clock.nowUtc();
+ runFlowAssertResponse(loadFile("domain_transfer_approve_response.xml"));
+ domain = reloadResourceByForeignKey();
+ DomainHistory acceptHistory =
+ getOnlyHistoryEntryOfType(domain, DOMAIN_TRANSFER_APPROVE, DomainHistory.class);
+ assertBillingEventsForResource(
+ domain,
+ new BillingEvent.OneTime.Builder()
+ .setBillingTime(now.plusDays(5))
+ .setEventTime(now)
+ .setRegistrarId("NewRegistrar")
+ .setCost(Money.of(USD, new BigDecimal("11.00")))
+ .setDomainHistory(acceptHistory)
+ .setReason(Reason.TRANSFER)
+ .setPeriodYears(1)
+ .setTargetId("example.tld")
+ .build(),
+ getGainingClientAutorenewEvent()
+ .asBuilder()
+ .setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
+ .setDomainHistory(acceptHistory)
+ .build(),
+ getLosingClientAutorenewEvent()
+ .asBuilder()
+ .setRecurrenceEndTime(now)
+ .setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
+ .build());
+ }
+
+ @Test
+ void testSuccess_specifiedPriceRenewalBehavior_carriesOver() throws Exception {
+ PremiumList pl =
+ PremiumListDao.save(
+ new PremiumList.Builder()
+ .setCurrency(USD)
+ .setName("tld")
+ .setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
+ .build());
+ persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
+ setupDomainWithPendingTransfer("example", "tld");
+ domain = loadByEntity(domain);
+ persistResource(
+ loadByKey(domain.getAutorenewBillingEvent())
+ .asBuilder()
+ .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
+ .setRenewalPrice(Money.of(USD, new BigDecimal("43.10")))
+ .build());
+ setEppInput("domain_transfer_approve_wildcard.xml", ImmutableMap.of("DOMAIN", "example.tld"));
+ DateTime now = clock.nowUtc();
+ runFlowAssertResponse(loadFile("domain_transfer_approve_response.xml"));
+ domain = reloadResourceByForeignKey();
+ DomainHistory acceptHistory =
+ getOnlyHistoryEntryOfType(domain, DOMAIN_TRANSFER_APPROVE, DomainHistory.class);
+ assertBillingEventsForResource(
+ domain,
+ new BillingEvent.OneTime.Builder()
+ .setBillingTime(now.plusDays(5))
+ .setEventTime(now)
+ .setRegistrarId("NewRegistrar")
+ .setCost(Money.of(USD, new BigDecimal("43.10")))
+ .setDomainHistory(acceptHistory)
+ .setReason(Reason.TRANSFER)
+ .setPeriodYears(1)
+ .setTargetId("example.tld")
+ .build(),
+ getGainingClientAutorenewEvent()
+ .asBuilder()
+ .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
+ .setRenewalPrice(Money.of(USD, new BigDecimal("43.10")))
+ .setDomainHistory(acceptHistory)
+ .build(),
+ getLosingClientAutorenewEvent()
+ .asBuilder()
+ .setRecurrenceEndTime(now)
+ .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
+ .setRenewalPrice(Money.of(USD, new BigDecimal("43.10")))
+ .build());
+ }
+
@Test
void testFailure_badContactPassword() {
// Change the contact's password so it does not match the password in the file.
diff --git a/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java
index 12c89d7a4..3790814c3 100644
--- a/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java
+++ b/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java
@@ -28,11 +28,13 @@ import static google.registry.model.tld.Registry.TldState.QUIET_PERIOD;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.assertBillingEvents;
import static google.registry.testing.DatabaseHelper.assertBillingEventsEqual;
+import static google.registry.testing.DatabaseHelper.assertBillingEventsForResource;
import static google.registry.testing.DatabaseHelper.assertPollMessagesEqual;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
import static google.registry.testing.DatabaseHelper.getPollMessages;
+import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.loadByKeys;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
@@ -83,6 +85,7 @@ import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
+import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
@@ -101,11 +104,14 @@ import google.registry.model.registrar.Registrar.State;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Registry;
+import google.registry.model.tld.label.PremiumList;
+import google.registry.model.tld.label.PremiumListDao;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
+import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
@@ -1202,6 +1208,117 @@ class DomainTransferRequestFlowTest
runTest("domain_transfer_request_fee.xml", UserPrivileges.SUPERUSER, RICH_DOMAIN_MAP);
}
+ @Test
+ void testSuccess_nonPremiumRenewalPrice_isReflectedInTransferCostAndCarriesOver()
+ throws Exception {
+ setupDomain("example", "tld");
+ PremiumList pl =
+ PremiumListDao.save(
+ new PremiumList.Builder()
+ .setCurrency(USD)
+ .setName("tld")
+ .setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
+ .build());
+ persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
+ domain = loadByEntity(domain);
+ persistResource(
+ loadByKey(domain.getAutorenewBillingEvent())
+ .asBuilder()
+ .setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
+ .build());
+ DateTime now = clock.nowUtc();
+
+ // This ensures that the transfer has non-premium cost, as otherwise, the fee extension would be
+ // required to ack the premium price.
+ setEppInput("domain_transfer_request.xml");
+ eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
+ runFlowAssertResponse(loadFile("domain_transfer_request_response.xml"));
+ domain = loadByEntity(domain);
+
+ DomainHistory requestHistory =
+ getOnlyHistoryEntryOfType(domain, DOMAIN_TRANSFER_REQUEST, DomainHistory.class);
+ // Check that the server approve billing recurrence (which will reify after 5 days if the
+ // transfer is not explicitly acked) maintains the non-premium behavior.
+ assertBillingEventsForResource(
+ domain,
+ new BillingEvent.OneTime.Builder()
+ .setBillingTime(now.plusDays(10)) // 5 day pending transfer + 5 day billing grace period
+ .setEventTime(now.plusDays(5))
+ .setRegistrarId("NewRegistrar")
+ .setCost(Money.of(USD, new BigDecimal("11.00")))
+ .setDomainHistory(requestHistory)
+ .setReason(Reason.TRANSFER)
+ .setPeriodYears(1)
+ .setTargetId("example.tld")
+ .build(),
+ getGainingClientAutorenewEvent()
+ .asBuilder()
+ .setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
+ .setDomainHistory(requestHistory)
+ .build(),
+ getLosingClientAutorenewEvent()
+ .asBuilder()
+ .setRecurrenceEndTime(now.plusDays(5))
+ .setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
+ .build());
+ }
+
+ @Test
+ void testSuccess_specifiedRenewalPrice_isReflectedInTransferCostAndCarriesOver()
+ throws Exception {
+ setupDomain("example", "tld");
+ PremiumList pl =
+ PremiumListDao.save(
+ new PremiumList.Builder()
+ .setCurrency(USD)
+ .setName("tld")
+ .setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
+ .build());
+ persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
+ domain = loadByEntity(domain);
+ persistResource(
+ loadByKey(domain.getAutorenewBillingEvent())
+ .asBuilder()
+ .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
+ .setRenewalPrice(Money.of(USD, new BigDecimal("18.79")))
+ .build());
+ DateTime now = clock.nowUtc();
+
+ setEppInput("domain_transfer_request.xml");
+ eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
+ runFlowAssertResponse(loadFile("domain_transfer_request_response.xml"));
+ domain = loadByEntity(domain);
+
+ DomainHistory requestHistory =
+ getOnlyHistoryEntryOfType(domain, DOMAIN_TRANSFER_REQUEST, DomainHistory.class);
+ // Check that the server approve billing recurrence (which will reify after 5 days if the
+ // transfer is not explicitly acked) maintains the non-premium behavior.
+ assertBillingEventsForResource(
+ domain,
+ new BillingEvent.OneTime.Builder()
+ .setBillingTime(now.plusDays(10)) // 5 day pending transfer + 5 day billing grace period
+ .setEventTime(now.plusDays(5))
+ .setRegistrarId("NewRegistrar")
+ .setCost(Money.of(USD, new BigDecimal("18.79")))
+ .setDomainHistory(requestHistory)
+ .setReason(Reason.TRANSFER)
+ .setPeriodYears(1)
+ .setTargetId("example.tld")
+ .build(),
+ getGainingClientAutorenewEvent()
+ .asBuilder()
+ .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
+ .setRenewalPrice(Money.of(USD, new BigDecimal("18.79")))
+ .setDomainHistory(requestHistory)
+ .build(),
+ getLosingClientAutorenewEvent()
+ .asBuilder()
+ .setRecurrenceEndTime(now.plusDays(5))
+ .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
+ .setRenewalPrice(Money.of(USD, new BigDecimal("18.79")))
+ .build());
+ }
+
private void runWrongCurrencyTest(Map substitutions) {
Map fullSubstitutions = Maps.newHashMap();
fullSubstitutions.putAll(substitutions);
diff --git a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_response_domain_exists_v06.xml b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_response_domain_exists_v06.xml
index 86f5fe1be..f52b0b616 100644
--- a/core/src/test/resources/google/registry/flows/domain/domain_check_fee_response_domain_exists_v06.xml
+++ b/core/src/test/resources/google/registry/flows/domain/domain_check_fee_response_domain_exists_v06.xml
@@ -33,10 +33,7 @@
USD
transfer
1
-
- 100.00
- premium
+ %RENEWPRICE%
rich.example
diff --git a/core/src/test/resources/google/registry/flows/domain/domain_transfer_approve_extra.xml b/core/src/test/resources/google/registry/flows/domain/domain_transfer_approve_wildcard.xml
similarity index 83%
rename from core/src/test/resources/google/registry/flows/domain/domain_transfer_approve_extra.xml
rename to core/src/test/resources/google/registry/flows/domain/domain_transfer_approve_wildcard.xml
index 396714d94..4a599e0b1 100644
--- a/core/src/test/resources/google/registry/flows/domain/domain_transfer_approve_extra.xml
+++ b/core/src/test/resources/google/registry/flows/domain/domain_transfer_approve_wildcard.xml
@@ -3,7 +3,7 @@
- example.extra
+ %DOMAIN%
ABC-12345