mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 00:17:20 +02:00
Allow domain transfers with 0 period and in auto-renew grace period
Normally, if a domain is in the auto-renew grace period, a transfer will cancel the auto-renew billing event. In the event of a transfer with no change to registration end date, the auto-renew billing event should not be cancelled and the gaining registrar should not be charged for the transfer. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=170576726
This commit is contained in:
parent
3c0b17dc6f
commit
7aa5629517
9 changed files with 120 additions and 50 deletions
|
@ -898,7 +898,6 @@ new ones with the correct approval time).
|
||||||
* Resource with this id does not exist.
|
* Resource with this id does not exist.
|
||||||
* 2304
|
* 2304
|
||||||
* Resource status prohibits this operation.
|
* Resource status prohibits this operation.
|
||||||
* Superuser extensions cannot be used during autorenew grace periods.
|
|
||||||
* Domain transfer period cannot be zero when using the fee transfer
|
* Domain transfer period cannot be zero when using the fee transfer
|
||||||
extension.
|
extension.
|
||||||
* The requested domain name is on the premium price list, and this
|
* The requested domain name is on the premium price list, and this
|
||||||
|
|
|
@ -138,9 +138,16 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||||
int extraYears = transferData.getTransferPeriod().getValue();
|
int extraYears = transferData.getTransferPeriod().getValue();
|
||||||
if (autorenewGrace != null) {
|
if (autorenewGrace != null) {
|
||||||
extraYears = 0;
|
extraYears = 0;
|
||||||
|
// During a normal transfer, if the domain is in the auto-renew grace period, the auto-renew
|
||||||
|
// billing event is cancelled and the gaining registrar is charged for the one year renewal.
|
||||||
|
// But, if the superuser extension is used to request a transfer without an additional year
|
||||||
|
// then the gaining registrar is not charged for the one year renewal and the losing registrar
|
||||||
|
// still needs to be charged for the auto-renew.
|
||||||
|
if (billingEvent.isPresent()) {
|
||||||
ofy().save().entity(
|
ofy().save().entity(
|
||||||
BillingEvent.Cancellation.forGracePeriod(autorenewGrace, historyEntry, targetId));
|
BillingEvent.Cancellation.forGracePeriod(autorenewGrace, historyEntry, targetId));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Close the old autorenew event and poll message at the transfer time (aka now). This may end
|
// Close the old autorenew event and poll message at the transfer time (aka now). This may end
|
||||||
// up deleting the poll message.
|
// up deleting the poll message.
|
||||||
updateAutorenewRecurrenceEndTime(existingDomain, now);
|
updateAutorenewRecurrenceEndTime(existingDomain, now);
|
||||||
|
|
|
@ -46,7 +46,6 @@ import google.registry.flows.annotations.ReportingSpec;
|
||||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||||
import google.registry.flows.exceptions.InvalidTransferPeriodValueException;
|
import google.registry.flows.exceptions.InvalidTransferPeriodValueException;
|
||||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||||
import google.registry.flows.exceptions.SuperuserExtensionAndAutorenewGracePeriodException;
|
|
||||||
import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
|
import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
|
||||||
import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException;
|
import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException;
|
||||||
import google.registry.model.domain.DomainCommand.Transfer;
|
import google.registry.model.domain.DomainCommand.Transfer;
|
||||||
|
@ -99,7 +98,6 @@ import org.joda.time.DateTime;
|
||||||
* @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException}
|
* @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException}
|
||||||
* @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException}
|
* @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException}
|
||||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||||
* @error {@link google.registry.flows.exceptions.SuperuserExtensionAndAutorenewGracePeriodException}
|
|
||||||
* @error {@link google.registry.flows.exceptions.TransferPeriodMustBeOneYearException}
|
* @error {@link google.registry.flows.exceptions.TransferPeriodMustBeOneYearException}
|
||||||
* @error {@link InvalidTransferPeriodValueException}
|
* @error {@link InvalidTransferPeriodValueException}
|
||||||
* @error {@link google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException}
|
* @error {@link google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException}
|
||||||
|
@ -184,10 +182,6 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||||
DomainResource domainAtTransferTime =
|
DomainResource domainAtTransferTime =
|
||||||
existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
||||||
if (!domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW).isEmpty()) {
|
if (!domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW).isEmpty()) {
|
||||||
if (superuserExtension != null) {
|
|
||||||
// We don't allow the superuser extension for domains in the auto renew grace period
|
|
||||||
throw new SuperuserExtensionAndAutorenewGracePeriodException();
|
|
||||||
}
|
|
||||||
extraYears = 0;
|
extraYears = 0;
|
||||||
}
|
}
|
||||||
// The new expiration time if there is a server approval.
|
// The new expiration time if there is a server approval.
|
||||||
|
|
|
@ -119,7 +119,7 @@ public final class DomainTransferUtils {
|
||||||
return builder
|
return builder
|
||||||
.addAll(
|
.addAll(
|
||||||
createOptionalAutorenewCancellation(
|
createOptionalAutorenewCancellation(
|
||||||
automaticTransferTime, historyEntry, targetId, existingDomain)
|
automaticTransferTime, historyEntry, targetId, existingDomain, transferCost)
|
||||||
.asSet())
|
.asSet())
|
||||||
.add(
|
.add(
|
||||||
createGainingClientAutorenewEvent(
|
createGainingClientAutorenewEvent(
|
||||||
|
@ -229,7 +229,9 @@ public final class DomainTransferUtils {
|
||||||
* expiration time. Since the gaining registrar will still be billed for the transfer's 1-year
|
* expiration time. Since the gaining registrar will still be billed for the transfer's 1-year
|
||||||
* renewal, we must issue a cancellation for the autorenew, so that the losing registrar will not
|
* renewal, we must issue a cancellation for the autorenew, so that the losing registrar will not
|
||||||
* be charged (essentially, the gaining registrar takes on the cost of the year of registration
|
* be charged (essentially, the gaining registrar takes on the cost of the year of registration
|
||||||
* that the autorenew just added).
|
* that the autorenew just added). But, if the superuser extension is used to request a transfer
|
||||||
|
* without an additional year then the gaining registrar is not charged for the one year renewal
|
||||||
|
* and the losing registrar still needs to be charged for the auto-renew.
|
||||||
*
|
*
|
||||||
* <p>For details on the policy justification, see b/19430703#comment17 and <a
|
* <p>For details on the policy justification, see b/19430703#comment17 and <a
|
||||||
* href="https://www.icann.org/news/advisory-2002-06-06-en">this ICANN advisory</a>.
|
* href="https://www.icann.org/news/advisory-2002-06-06-en">this ICANN advisory</a>.
|
||||||
|
@ -238,13 +240,14 @@ public final class DomainTransferUtils {
|
||||||
DateTime automaticTransferTime,
|
DateTime automaticTransferTime,
|
||||||
HistoryEntry historyEntry,
|
HistoryEntry historyEntry,
|
||||||
String targetId,
|
String targetId,
|
||||||
DomainResource existingDomain) {
|
DomainResource existingDomain,
|
||||||
|
Optional<Money> transferCost) {
|
||||||
DomainResource domainAtTransferTime =
|
DomainResource domainAtTransferTime =
|
||||||
existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
||||||
GracePeriod autorenewGracePeriod =
|
GracePeriod autorenewGracePeriod =
|
||||||
getOnlyElement(
|
getOnlyElement(
|
||||||
domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW), null);
|
domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW), null);
|
||||||
if (autorenewGracePeriod != null) {
|
if (autorenewGracePeriod != null && transferCost.isPresent()) {
|
||||||
return Optional.of(
|
return Optional.of(
|
||||||
BillingEvent.Cancellation.forGracePeriod(autorenewGracePeriod, historyEntry, targetId)
|
BillingEvent.Cancellation.forGracePeriod(autorenewGracePeriod, historyEntry, targetId)
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package google.registry.flows.exceptions;
|
|
||||||
|
|
||||||
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
|
||||||
|
|
||||||
/** Superuser extensions cannot be used during autorenew grace periods. */
|
|
||||||
public class SuperuserExtensionAndAutorenewGracePeriodException extends
|
|
||||||
StatusProhibitsOperationException {
|
|
||||||
public SuperuserExtensionAndAutorenewGracePeriodException() {
|
|
||||||
super("Superuser extensions cannot be used during autorenew grace periods.");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -42,6 +42,7 @@ import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.ImmutableSortedMap;
|
import com.google.common.collect.ImmutableSortedMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Ordering;
|
import com.google.common.collect.Ordering;
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||||
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
|
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
|
||||||
|
@ -52,6 +53,7 @@ import google.registry.model.billing.BillingEvent.Cancellation;
|
||||||
import google.registry.model.billing.BillingEvent.Cancellation.Builder;
|
import google.registry.model.billing.BillingEvent.Cancellation.Builder;
|
||||||
import google.registry.model.billing.BillingEvent.OneTime;
|
import google.registry.model.billing.BillingEvent.OneTime;
|
||||||
import google.registry.model.billing.BillingEvent.Reason;
|
import google.registry.model.billing.BillingEvent.Reason;
|
||||||
|
import google.registry.model.billing.BillingEvent.Recurring;
|
||||||
import google.registry.model.contact.ContactAuthInfo;
|
import google.registry.model.contact.ContactAuthInfo;
|
||||||
import google.registry.model.domain.DomainAuthInfo;
|
import google.registry.model.domain.DomainAuthInfo;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
|
@ -568,7 +570,6 @@ public class DomainTransferApproveFlowTest
|
||||||
previousSuccessRecord.asBuilder().setReportAmount(-1).build(),
|
previousSuccessRecord.asBuilder().setReportAmount(-1).build(),
|
||||||
DomainTransactionRecord.create(
|
DomainTransactionRecord.create(
|
||||||
"tld", clock.nowUtc().plusDays(3), TRANSFER_SUCCESSFUL, 1));
|
"tld", clock.nowUtc().plusDays(3), TRANSFER_SUCCESSFUL, 1));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -589,4 +590,37 @@ public class DomainTransferApproveFlowTest
|
||||||
domain.getRegistrationExpirationTime().plusYears(0));
|
domain.getRegistrationExpirationTime().plusYears(0));
|
||||||
assertHistoryEntriesDoNotContainTransferBillingEventsOrGracePeriods();
|
assertHistoryEntriesDoNotContainTransferBillingEventsOrGracePeriods();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSuccess_superuserExtension_transferPeriodZero_autorenewGraceActive()
|
||||||
|
throws Exception {
|
||||||
|
DomainResource domain = reloadResourceByForeignKey();
|
||||||
|
Key<Recurring> existingAutorenewEvent = domain.getAutorenewBillingEvent();
|
||||||
|
// Set domain to have auto-renewed just before the transfer request, so that it will have an
|
||||||
|
// active autorenew grace period spanning the entire transfer window.
|
||||||
|
DateTime autorenewTime = clock.nowUtc().minusDays(1);
|
||||||
|
DateTime expirationTime = autorenewTime.plusYears(1);
|
||||||
|
TransferData.Builder transferDataBuilder = domain.getTransferData().asBuilder();
|
||||||
|
domain =
|
||||||
|
persistResource(
|
||||||
|
domain
|
||||||
|
.asBuilder()
|
||||||
|
.setTransferData(
|
||||||
|
transferDataBuilder.setTransferPeriod(Period.create(0, Unit.YEARS)).build())
|
||||||
|
.setRegistrationExpirationTime(expirationTime)
|
||||||
|
.addGracePeriod(
|
||||||
|
GracePeriod.createForRecurring(
|
||||||
|
GracePeriodStatus.AUTO_RENEW,
|
||||||
|
autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()),
|
||||||
|
"TheRegistrar",
|
||||||
|
existingAutorenewEvent))
|
||||||
|
.build());
|
||||||
|
clock.advanceOneMilli();
|
||||||
|
runSuccessfulFlowWithAssertions(
|
||||||
|
"tld",
|
||||||
|
"domain_transfer_approve.xml",
|
||||||
|
"domain_transfer_approve_response_zero_period_autorenew_grace.xml",
|
||||||
|
domain.getRegistrationExpirationTime());
|
||||||
|
assertHistoryEntriesDoNotContainTransferBillingEventsOrGracePeriods();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,6 @@ import google.registry.flows.exceptions.InvalidTransferPeriodValueException;
|
||||||
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
|
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
|
||||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||||
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||||
import google.registry.flows.exceptions.SuperuserExtensionAndAutorenewGracePeriodException;
|
|
||||||
import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
|
import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
|
||||||
import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException;
|
import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException;
|
||||||
import google.registry.model.billing.BillingEvent;
|
import google.registry.model.billing.BillingEvent;
|
||||||
|
@ -713,20 +712,33 @@ public class DomainTransferRequestFlowTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFailure_superuserExtension_duringAutorenewGracePeriod() throws Exception {
|
public void testSuccess_superuserExtension_zeroPeriod_autorenewGraceActive()
|
||||||
setupDomain("example", "tld");
|
throws Exception {
|
||||||
eppRequestSource = EppRequestSource.TOOL;
|
eppRequestSource = EppRequestSource.TOOL;
|
||||||
DomainResource domain = reloadResourceByForeignKey();
|
setupDomain("example", "tld");
|
||||||
DateTime oldExpirationTime = clock.nowUtc().minusDays(1);
|
Key<BillingEvent.Recurring> existingAutorenewEvent =
|
||||||
persistResource(domain.asBuilder()
|
domain.getAutorenewBillingEvent();
|
||||||
.setRegistrationExpirationTime(oldExpirationTime)
|
// Set domain to have auto-renewed just before the transfer request, so that it will have an
|
||||||
|
// active autorenew grace period spanning the entire transfer window.
|
||||||
|
DateTime autorenewTime = clock.nowUtc().minusDays(1);
|
||||||
|
DateTime expirationTime = autorenewTime.plusYears(1);
|
||||||
|
domain = persistResource(domain.asBuilder()
|
||||||
|
.setRegistrationExpirationTime(expirationTime)
|
||||||
|
.addGracePeriod(GracePeriod.createForRecurring(
|
||||||
|
GracePeriodStatus.AUTO_RENEW,
|
||||||
|
autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()),
|
||||||
|
"TheRegistrar",
|
||||||
|
existingAutorenewEvent))
|
||||||
.build());
|
.build());
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
thrown.expect(SuperuserExtensionAndAutorenewGracePeriodException.class);
|
doSuccessfulSuperuserExtensionTest(
|
||||||
runTest(
|
|
||||||
"domain_transfer_request_superuser_extension.xml",
|
"domain_transfer_request_superuser_extension.xml",
|
||||||
UserPrivileges.SUPERUSER,
|
"domain_transfer_request_response_su_ext_zero_period_autorenew_grace.xml",
|
||||||
ImmutableMap.of("PERIOD", "1", "AUTOMATIC_TRANSFER_LENGTH", "5"));
|
domain.getRegistrationExpirationTime(),
|
||||||
|
ImmutableMap.of("PERIOD", "0", "AUTOMATIC_TRANSFER_LENGTH", "0"),
|
||||||
|
Optional.<Money>absent(),
|
||||||
|
Period.create(0, Unit.YEARS),
|
||||||
|
Duration.standardDays(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||||
|
<response>
|
||||||
|
<result code="1000">
|
||||||
|
<msg>Command completed successfully</msg>
|
||||||
|
</result>
|
||||||
|
<resData>
|
||||||
|
<domain:trnData
|
||||||
|
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||||
|
<domain:name>example.tld</domain:name>
|
||||||
|
<domain:trStatus>clientApproved</domain:trStatus>
|
||||||
|
<domain:reID>NewRegistrar</domain:reID>
|
||||||
|
<domain:reDate>2000-06-06T22:00:00.0Z</domain:reDate>
|
||||||
|
<domain:acID>TheRegistrar</domain:acID>
|
||||||
|
<domain:acDate>2000-06-09T22:00:00.0Z</domain:acDate>
|
||||||
|
<domain:exDate>2001-06-08T22:00:00.0Z</domain:exDate>
|
||||||
|
</domain:trnData>
|
||||||
|
</resData>
|
||||||
|
<trID>
|
||||||
|
<clTRID>ABC-12345</clTRID>
|
||||||
|
<svTRID>server-trid</svTRID>
|
||||||
|
</trID>
|
||||||
|
</response>
|
||||||
|
</epp>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||||
|
<response>
|
||||||
|
<result code="1001">
|
||||||
|
<msg>Command completed successfully; action pending</msg>
|
||||||
|
</result>
|
||||||
|
<resData>
|
||||||
|
<domain:trnData
|
||||||
|
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||||
|
<domain:name>example.tld</domain:name>
|
||||||
|
<domain:trStatus>pending</domain:trStatus>
|
||||||
|
<domain:reID>NewRegistrar</domain:reID>
|
||||||
|
<domain:reDate>2000-06-09T22:00:00.0Z</domain:reDate>
|
||||||
|
<domain:acID>TheRegistrar</domain:acID>
|
||||||
|
<domain:acDate>2000-06-09T22:00:00.0Z</domain:acDate>
|
||||||
|
<domain:exDate>2001-06-08T22:00:00.0Z</domain:exDate>
|
||||||
|
</domain:trnData>
|
||||||
|
</resData>
|
||||||
|
<trID>
|
||||||
|
<clTRID>ABC-12345</clTRID>
|
||||||
|
<svTRID>server-trid</svTRID>
|
||||||
|
</trID>
|
||||||
|
</response>
|
||||||
|
</epp>
|
Loading…
Add table
Add a link
Reference in a new issue