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 e26467b6c..b647eaaa2 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java @@ -49,6 +49,7 @@ import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.billing.BillingEvent.Recurring; +import google.registry.model.billing.BillingEvent.RenewalPriceBehavior; import google.registry.model.domain.Domain; import google.registry.model.domain.DomainHistory; import google.registry.model.domain.GracePeriod; @@ -67,6 +68,7 @@ import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.TransferStatus; import java.util.Optional; import javax.inject.Inject; +import org.joda.money.Money; import org.joda.time.DateTime; /** @@ -147,6 +149,8 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent()); HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain); historyBuilder.setRevisionId(domainHistoryId.getRevisionId()); + boolean hasPackageToken = existingDomain.getCurrentPackageToken().isPresent(); + Money renewalPrice = hasPackageToken ? null : existingRecurring.getRenewalPrice().orElse(null); Optional billingEvent = transferData.getTransferPeriod().getValue() == 0 ? Optional.empty() @@ -162,12 +166,16 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { Registry.get(tld), targetId, transferData.getTransferRequestTime(), - existingRecurring) + // When removing a domain from a package it should return to the + // default recurring billing behavior so the existing recurring + // billing event should not be passed in. + hasPackageToken ? null : existingRecurring) .getRenewCost()) .setEventTime(now) .setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength())) .setDomainHistoryId(domainHistoryId) .build()); + ImmutableList.Builder entitiesToSave = new ImmutableList.Builder<>(); // If we are within an autorenew grace period, cancel the autorenew billing event and don't // increase the registration time, since the transfer subsumes the autorenew's extra year. @@ -198,8 +206,11 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { .setTargetId(targetId) .setRegistrarId(gainingRegistrarId) .setEventTime(newExpirationTime) - .setRenewalPriceBehavior(existingRecurring.getRenewalPriceBehavior()) - .setRenewalPrice(existingRecurring.getRenewalPrice().orElse(null)) + .setRenewalPriceBehavior( + hasPackageToken + ? RenewalPriceBehavior.DEFAULT + : existingRecurring.getRenewalPriceBehavior()) + .setRenewalPrice(renewalPrice) .setRecurrenceEndTime(END_OF_TIME) .setDomainHistoryId(domainHistoryId) .build(); @@ -243,7 +254,11 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { .orElseGet(ImmutableSet::of)) .setLastEppUpdateTime(now) .setLastEppUpdateRegistrarId(registrarId) + // Even if the existing domain had a package token, that package token should be removed + // on transfer + .setCurrentPackageToken(null) .build(); + Registry registry = Registry.get(existingDomain.getTld()); DomainHistory domainHistory = buildDomainHistory(newDomain, registry, now, gainingRegistrarId); // Create a poll message for the gaining client. 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 d4d36a2fa..e8661d151 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java @@ -16,6 +16,8 @@ package google.registry.flows.domain; import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE; import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE; import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.NET_ADDS_4_YR; @@ -374,11 +376,91 @@ class DomainTransferApproveFlowTest dryRunFlowAssertResponse(loadFile("domain_transfer_approve_response.xml")); } + @Test + void testDryRun_PackageDomain() throws Exception { + AllocationToken allocationToken = + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .setTokenType(PACKAGE) + .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) + .setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar")) + .build()); + domain = reloadResourceByForeignKey(); + persistResource( + loadByKey(domain.getAutorenewBillingEvent()) + .asBuilder() + .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) + .setRenewalPrice(Money.of(USD, new BigDecimal("10.00"))) + .build()); + persistResource( + domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build()); + clock.advanceOneMilli(); + setEppInput("domain_transfer_approve_wildcard.xml", ImmutableMap.of("DOMAIN", "example.tld")); + dryRunFlowAssertResponse(loadFile("domain_transfer_approve_response.xml")); + } + @Test void testSuccess() throws Exception { doSuccessfulTest("tld", "domain_transfer_approve.xml", "domain_transfer_approve_response.xml"); } + @Test + void testSuccess_removesPackageToken() throws Exception { + AllocationToken allocationToken = + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .setTokenType(PACKAGE) + .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) + .setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar")) + .build()); + domain = reloadResourceByForeignKey(); + persistResource( + loadByKey(domain.getAutorenewBillingEvent()) + .asBuilder() + .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) + .setRenewalPrice(Money.of(USD, new BigDecimal("10.00"))) + .build()); + persistResource( + domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build()); + clock.advanceOneMilli(); + 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.DEFAULT) + .setRenewalPrice(null) + .setDomainHistory(acceptHistory) + .build(), + getLosingClientAutorenewEvent() + .asBuilder() + .setRecurrenceEndTime(now) + .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) + .setRenewalPrice(Money.of(USD, new BigDecimal("10.00"))) + .build()); + assertThat(domain.getCurrentPackageToken()).isEmpty(); + assertThat(domain.getCurrentSponsorRegistrarId()).isEqualTo("NewRegistrar"); + assertThat(loadByKey(domain.getAutorenewBillingEvent()).getRenewalPriceBehavior()) + .isEqualTo(RenewalPriceBehavior.DEFAULT); + } + @Test void testSuccess_nonDefaultTransferGracePeriod() throws Exception { // We have to set up a new domain in a different TLD so that the billing event will be persisted