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 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