1
0
mirror of https://github.com/google/nomulus synced 2026-02-10 06:50:30 +00:00

Make domain transfers use (and retain) the renewal price/behavior (#1701)

* Use the new renewal price logic in transfer flow

* Fix build

* Add renewal handling on all transfer flows

* Merge branch 'master' into transfer-retain-renewal-price

* Merge branch 'master' into transfer-retain-renewal-price

* Add more tests
This commit is contained in:
Ben McIlwain
2022-08-05 15:53:27 -04:00
committed by GitHub
parent eb1a314666
commit 9ff25f9a67
15 changed files with 409 additions and 30 deletions

View File

@@ -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());
}
}

View File

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

View File

@@ -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<String, String> substitutions) {
Map<String, String> fullSubstitutions = Maps.newHashMap();
fullSubstitutions.putAll(substitutions);

View File

@@ -33,10 +33,7 @@
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
<!-- TODO(mcilwain): This should be non-premium once transfer flow
changes are made. -->
<fee:fee description="renew">100.00</fee:fee>
<fee:class>premium</fee:class>
<fee:fee description="renew">%RENEWPRICE%</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>rich.example</fee:name>

View File

@@ -3,7 +3,7 @@
<transfer op="approve">
<domain:transfer
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.extra</domain:name>
<domain:name>%DOMAIN%</domain:name>
</domain:transfer>
</transfer>
<clTRID>ABC-12345</clTRID>