Add pricing logic for allocation tokens in domain renew (#1961)

* Add pricing logic for allocation tokens in domain renew

* Add clarifying comment

* Several fixes

* Add test for renewalPriceBehavior not changing
This commit is contained in:
sarahcaseybot 2023-03-23 14:00:36 -04:00 committed by GitHub
parent 273a633847
commit 10b9951638
10 changed files with 480 additions and 62 deletions

View file

@ -36,6 +36,7 @@ import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.flows.custom.CustomLogicFactoryModule; import google.registry.flows.custom.CustomLogicFactoryModule;
import google.registry.flows.custom.CustomLogicModule; import google.registry.flows.custom.CustomLogicModule;
import google.registry.flows.domain.DomainPricingLogic; import google.registry.flows.domain.DomainPricingLogic;
import google.registry.flows.domain.DomainPricingLogic.AllocationTokenInvalidForPremiumNameException;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent.Cancellation; import google.registry.model.billing.BillingEvent.Cancellation;
import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Flag;
@ -53,6 +54,7 @@ import google.registry.util.Clock;
import google.registry.util.SystemClock; import google.registry.util.SystemClock;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.apache.beam.sdk.Pipeline; import org.apache.beam.sdk.Pipeline;
@ -383,24 +385,31 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
// It is OK to always create a OneTime, even though the domain might be deleted or transferred // It is OK to always create a OneTime, even though the domain might be deleted or transferred
// later during autorenew grace period, as a cancellation will always be written out in those // later during autorenew grace period, as a cancellation will always be written out in those
// instances. // instances.
OneTime oneTime = OneTime oneTime = null;
new OneTime.Builder() try {
.setBillingTime(billingTime) oneTime =
.setRegistrarId(recurring.getRegistrarId()) new OneTime.Builder()
// Determine the cost for a one-year renewal. .setBillingTime(billingTime)
.setCost( .setRegistrarId(recurring.getRegistrarId())
domainPricingLogic // Determine the cost for a one-year renewal.
.getRenewPrice(tld, recurring.getTargetId(), eventTime, 1, recurring) .setCost(
.getRenewCost()) domainPricingLogic
.setEventTime(eventTime) .getRenewPrice(
.setFlags(union(recurring.getFlags(), Flag.SYNTHETIC)) tld, recurring.getTargetId(), eventTime, 1, recurring, Optional.empty())
.setDomainHistory(historyEntry) .getRenewCost())
.setPeriodYears(1) .setEventTime(eventTime)
.setReason(recurring.getReason()) .setFlags(union(recurring.getFlags(), Flag.SYNTHETIC))
.setSyntheticCreationTime(endTime) .setDomainHistory(historyEntry)
.setCancellationMatchingBillingEvent(recurring) .setPeriodYears(1)
.setTargetId(recurring.getTargetId()) .setReason(recurring.getReason())
.build(); .setSyntheticCreationTime(endTime)
.setCancellationMatchingBillingEvent(recurring)
.setTargetId(recurring.getTargetId())
.build();
} catch (AllocationTokenInvalidForPremiumNameException e) {
// This should not be reached since we are not using an allocation token
return;
}
results.add(oneTime); results.add(oneTime);
} }
results.add( results.add(

View file

@ -265,6 +265,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
validateLaunchCreateNotice(launchCreate.get().getNotice(), domainLabel, isSuperuser, now); validateLaunchCreateNotice(launchCreate.get().getNotice(), domainLabel, isSuperuser, now);
} }
boolean isSunriseCreate = hasSignedMarks && (tldState == START_DATE_SUNRISE); boolean isSunriseCreate = hasSignedMarks && (tldState == START_DATE_SUNRISE);
// TODO(sarahbot@): Add check for valid EPP actions on the token
Optional<AllocationToken> allocationToken = Optional<AllocationToken> allocationToken =
allocationTokenFlowUtils.verifyAllocationTokenCreateIfPresent( allocationTokenFlowUtils.verifyAllocationTokenCreateIfPresent(
command, command,

View file

@ -674,6 +674,8 @@ public class DomainFlowUtils {
String feeClass = null; String feeClass = null;
ImmutableList<Fee> fees = ImmutableList.of(); ImmutableList<Fee> fees = ImmutableList.of();
switch (feeRequest.getCommandName()) { switch (feeRequest.getCommandName()) {
// TODO(sarahbot@): Add check of valid EPP actions on token before passing the token to the
// fee request.
case CREATE: case CREATE:
// Don't return a create price for reserved names. // Don't return a create price for reserved names.
if (isReserved(domainName, isSunrise) && !isAvailable) { if (isReserved(domainName, isSunrise) && !isAvailable) {
@ -698,7 +700,8 @@ public class DomainFlowUtils {
builder.setAvailIfSupported(true); builder.setAvailIfSupported(true);
fees = fees =
pricingLogic pricingLogic
.getRenewPrice(registry, domainNameString, now, years, recurringBillingEvent) .getRenewPrice(
registry, domainNameString, now, years, recurringBillingEvent, allocationToken)
.getFees(); .getFees();
break; break;
case RESTORE: case RESTORE:

View file

@ -34,6 +34,7 @@ import google.registry.model.domain.fee.BaseFee;
import google.registry.model.domain.fee.BaseFee.FeeType; import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee; import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.token.AllocationToken; import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenBehavior;
import google.registry.model.pricing.PremiumPricingEngine.DomainPrices; import google.registry.model.pricing.PremiumPricingEngine.DomainPrices;
import google.registry.model.tld.Registry; import google.registry.model.tld.Registry;
import java.math.RoundingMode; import java.math.RoundingMode;
@ -112,21 +113,22 @@ public final class DomainPricingLogic {
String domainName, String domainName,
DateTime dateTime, DateTime dateTime,
int years, int years,
@Nullable Recurring recurringBillingEvent) { @Nullable Recurring recurringBillingEvent,
Optional<AllocationToken> allocationToken)
throws AllocationTokenInvalidForPremiumNameException {
checkArgument(years > 0, "Number of years must be positive"); checkArgument(years > 0, "Number of years must be positive");
Money renewCost; Money renewCost;
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
boolean isRenewCostPremiumPrice; boolean isRenewCostPremiumPrice;
// recurring billing event is null if the domain is still available. Billing events are created // recurring billing event is null if the domain is still available. Billing events are created
// in the process of domain creation. // in the process of domain creation.
if (recurringBillingEvent == null) { if (recurringBillingEvent == null) {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime); renewCost = getDomainRenewCostWithDiscount(domainPrices, years, allocationToken);
renewCost = domainPrices.getRenewCost().multipliedBy(years);
isRenewCostPremiumPrice = domainPrices.isPremium(); isRenewCostPremiumPrice = domainPrices.isPremium();
} else { } else {
switch (recurringBillingEvent.getRenewalPriceBehavior()) { switch (recurringBillingEvent.getRenewalPriceBehavior()) {
case DEFAULT: case DEFAULT:
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime); renewCost = getDomainRenewCostWithDiscount(domainPrices, years, allocationToken);
renewCost = domainPrices.getRenewCost().multipliedBy(years);
isRenewCostPremiumPrice = domainPrices.isPremium(); isRenewCostPremiumPrice = domainPrices.isPremium();
break; break;
// if the renewal price behavior is specified, then the renewal price should be the same // if the renewal price behavior is specified, then the renewal price should be the same
@ -136,6 +138,7 @@ public final class DomainPricingLogic {
recurringBillingEvent.getRenewalPrice(), recurringBillingEvent.getRenewalPrice(),
"Unexpected behavior: renewal price cannot be null when renewal behavior is" "Unexpected behavior: renewal price cannot be null when renewal behavior is"
+ " SPECIFIED"); + " SPECIFIED");
// Don't apply allocation token to renewal price when SPECIFIED
renewCost = recurringBillingEvent.getRenewalPrice().get().multipliedBy(years); renewCost = recurringBillingEvent.getRenewalPrice().get().multipliedBy(years);
isRenewCostPremiumPrice = false; isRenewCostPremiumPrice = false;
break; break;
@ -143,9 +146,11 @@ public final class DomainPricingLogic {
// at standard price of domains at the time, even if the domain is premium // at standard price of domains at the time, even if the domain is premium
case NONPREMIUM: case NONPREMIUM:
renewCost = renewCost =
Registry.get(getTldFromDomainName(domainName)) getDomainCostWithDiscount(
.getStandardRenewCost(dateTime) false,
.multipliedBy(years); years,
allocationToken,
Registry.get(getTldFromDomainName(domainName)).getStandardRenewCost(dateTime));
isRenewCostPremiumPrice = false; isRenewCostPremiumPrice = false;
break; break;
default: default:
@ -202,7 +207,7 @@ public final class DomainPricingLogic {
@Nullable Recurring recurringBillingEvent) @Nullable Recurring recurringBillingEvent)
throws EppException { throws EppException {
FeesAndCredits renewPrice = FeesAndCredits renewPrice =
getRenewPrice(registry, domainName, dateTime, 1, recurringBillingEvent); getRenewPrice(registry, domainName, dateTime, 1, recurringBillingEvent, Optional.empty());
return customLogic.customizeTransferPrice( return customLogic.customizeTransferPrice(
TransferPriceParameters.newBuilder() TransferPriceParameters.newBuilder()
.setFeesAndCredits( .setFeesAndCredits(
@ -242,25 +247,40 @@ public final class DomainPricingLogic {
private Money getDomainCreateCostWithDiscount( private Money getDomainCreateCostWithDiscount(
DomainPrices domainPrices, int years, Optional<AllocationToken> allocationToken) DomainPrices domainPrices, int years, Optional<AllocationToken> allocationToken)
throws EppException { throws EppException {
return getDomainCostWithDiscount(
domainPrices.isPremium(), years, allocationToken, domainPrices.getCreateCost());
}
/** Returns the domain renew cost with allocation-token-related discounts applied. */
private Money getDomainRenewCostWithDiscount(
DomainPrices domainPrices, int years, Optional<AllocationToken> allocationToken)
throws AllocationTokenInvalidForPremiumNameException {
return getDomainCostWithDiscount(
domainPrices.isPremium(), years, allocationToken, domainPrices.getRenewCost());
}
private Money getDomainCostWithDiscount(
boolean isPremium, int years, Optional<AllocationToken> allocationToken, Money oneYearCost)
throws AllocationTokenInvalidForPremiumNameException {
if (allocationToken.isPresent() if (allocationToken.isPresent()
&& allocationToken.get().getDiscountFraction() != 0.0 && allocationToken.get().getDiscountFraction() != 0.0
&& domainPrices.isPremium() && isPremium
&& !allocationToken.get().shouldDiscountPremiums()) { && !allocationToken.get().shouldDiscountPremiums()) {
throw new AllocationTokenInvalidForPremiumNameException(); throw new AllocationTokenInvalidForPremiumNameException();
} }
Money oneYearCreateCost = domainPrices.getCreateCost(); Money totalDomainFlowCost = oneYearCost.multipliedBy(years);
Money totalDomainCreateCost = oneYearCreateCost.multipliedBy(years);
// Apply the allocation token discount, if applicable. // Apply the allocation token discount, if applicable.
if (allocationToken.isPresent()) { if (allocationToken.isPresent()
&& allocationToken.get().getTokenBehavior().equals(TokenBehavior.DEFAULT)) {
int discountedYears = Math.min(years, allocationToken.get().getDiscountYears()); int discountedYears = Math.min(years, allocationToken.get().getDiscountYears());
Money discount = Money discount =
oneYearCreateCost.multipliedBy( oneYearCost.multipliedBy(
discountedYears * allocationToken.get().getDiscountFraction(), discountedYears * allocationToken.get().getDiscountFraction(),
RoundingMode.HALF_EVEN); RoundingMode.HALF_EVEN);
totalDomainCreateCost = totalDomainCreateCost.minus(discount); totalDomainFlowCost = totalDomainFlowCost.minus(discount);
} }
return totalDomainCreateCost; return totalDomainFlowCost;
} }
/** An allocation token was provided that is invalid for premium domains. */ /** An allocation token was provided that is invalid for premium domains. */

View file

@ -172,6 +172,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
Renew command = (Renew) resourceCommand; Renew command = (Renew) resourceCommand;
// Loads the target resource if it exists // Loads the target resource if it exists
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now); Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
// TODO(sarahbot@): Add check for valid EPP actions on the token
Optional<AllocationToken> allocationToken = Optional<AllocationToken> allocationToken =
allocationTokenFlowUtils.verifyAllocationTokenIfPresent( allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain, existingDomain,
@ -198,7 +199,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
targetId, targetId,
now, now,
years, years,
existingRecurringBillingEvent); existingRecurringBillingEvent,
allocationToken);
validateFeeChallenge(feeRenew, feesAndCredits, false); validateFeeChallenge(feeRenew, feesAndCredits, false);
flowCustomLogic.afterValidation( flowCustomLogic.afterValidation(
AfterValidationParameters.newBuilder() AfterValidationParameters.newBuilder()

View file

@ -1577,7 +1577,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
runFlowAssertResponse( runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld"))); loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
BillingEvent.OneTime billingEvent = BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class))); Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld"); assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5))); assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5)));
} }
@ -1627,7 +1627,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.put("EXDATE", "2004-04-03T22:00:00.0Z") .put("EXDATE", "2004-04-03T22:00:00.0Z")
.build())); .build()));
BillingEvent.OneTime billingEvent = BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class))); Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld"); assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getCost()).isEqualTo(expectedPrice); assertThat(billingEvent.getCost()).isEqualTo(expectedPrice);
} }
@ -1660,7 +1660,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_response_premium.xml", "domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "104.00"))); ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "104.00")));
BillingEvent.OneTime billingEvent = BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class))); Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("rich.example"); assertThat(billingEvent.getTargetId()).isEqualTo("rich.example");
// 1yr @ $100 + 2yrs @ $100 * (1 - 0.98) = $104 // 1yr @ $100 + 2yrs @ $100 * (1 - 0.98) = $104
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 104.00)); assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 104.00));
@ -1693,7 +1693,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_response_premium.xml", "domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "204.44"))); ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "204.44")));
BillingEvent.OneTime billingEvent = BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class))); Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("rich.example"); assertThat(billingEvent.getTargetId()).isEqualTo("rich.example");
// 2yrs @ $100 + 1yr @ $100 * (1 - 0.95555) = $204.44 // 2yrs @ $100 + 1yr @ $100 * (1 - 0.95555) = $204.44
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 204.44)); assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 204.44));
@ -1862,7 +1862,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
runFlowAssertResponse( runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld"))); loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
BillingEvent.OneTime billingEvent = BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class))); Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld"); assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5))); assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5)));
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo("abc123"); assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo("abc123");
@ -2058,7 +2058,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.put("EXDATE", "2001-04-03T22:00:00.0Z") .put("EXDATE", "2001-04-03T22:00:00.0Z")
.build())); .build()));
BillingEvent.OneTime billingEvent = BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class))); Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld"); assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo(token); assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo(token);
return billingEvent; return billingEvent;

View file

@ -20,6 +20,7 @@ import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DE
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM; import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED; import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.fee.BaseFee.FeeType.RENEW; import static google.registry.model.domain.fee.BaseFee.FeeType.RENEW;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistPremiumList; import static google.registry.testing.DatabaseHelper.persistPremiumList;
@ -32,11 +33,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableSet; 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 google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.FlowMetadata; import google.registry.flows.FlowMetadata;
import google.registry.flows.HttpSessionMetadata; import google.registry.flows.HttpSessionMetadata;
import google.registry.flows.SessionMetadata; import google.registry.flows.SessionMetadata;
import google.registry.flows.custom.DomainPricingCustomLogic; import google.registry.flows.custom.DomainPricingCustomLogic;
import google.registry.flows.domain.DomainPricingLogic.AllocationTokenInvalidForPremiumNameException;
import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent;
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.billing.BillingEvent.Recurring;
@ -44,6 +47,7 @@ import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.domain.Domain; import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory; import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.fee.Fee; import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.eppinput.EppInput; import google.registry.model.eppinput.EppInput;
import google.registry.model.tld.Registry; import google.registry.model.tld.Registry;
import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions;
@ -133,7 +137,8 @@ public class DomainPricingLogicTest {
void testGetDomainRenewPrice_oneYear_standardDomain_noBilling_isStandardPrice() void testGetDomainRenewPrice_oneYear_standardDomain_noBilling_isStandardPrice()
throws EppException { throws EppException {
assertThat( assertThat(
domainPricingLogic.getRenewPrice(registry, "standard.example", clock.nowUtc(), 1, null)) domainPricingLogic.getRenewPrice(
registry, "standard.example", clock.nowUtc(), 1, null, Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -145,7 +150,8 @@ public class DomainPricingLogicTest {
void testGetDomainRenewPrice_multiYear_standardDomain_noBilling_isStandardPrice() void testGetDomainRenewPrice_multiYear_standardDomain_noBilling_isStandardPrice()
throws EppException { throws EppException {
assertThat( assertThat(
domainPricingLogic.getRenewPrice(registry, "standard.example", clock.nowUtc(), 5, null)) domainPricingLogic.getRenewPrice(
registry, "standard.example", clock.nowUtc(), 5, null, Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -157,7 +163,8 @@ public class DomainPricingLogicTest {
void testGetDomainRenewPrice_oneYear_premiumDomain_noBilling_isPremiumPrice() void testGetDomainRenewPrice_oneYear_premiumDomain_noBilling_isPremiumPrice()
throws EppException { throws EppException {
assertThat( assertThat(
domainPricingLogic.getRenewPrice(registry, "premium.example", clock.nowUtc(), 1, null)) domainPricingLogic.getRenewPrice(
registry, "premium.example", clock.nowUtc(), 1, null, Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -169,7 +176,8 @@ public class DomainPricingLogicTest {
void testGetDomainRenewPrice_multiYear_premiumDomain_noBilling_isPremiumPrice() void testGetDomainRenewPrice_multiYear_premiumDomain_noBilling_isPremiumPrice()
throws EppException { throws EppException {
assertThat( assertThat(
domainPricingLogic.getRenewPrice(registry, "premium.example", clock.nowUtc(), 5, null)) domainPricingLogic.getRenewPrice(
registry, "premium.example", clock.nowUtc(), 5, null, Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -186,7 +194,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
1, 1,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"premium.example", DEFAULT, Optional.empty()))) "premium.example", DEFAULT, Optional.empty()),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -194,6 +203,58 @@ public class DomainPricingLogicTest {
.build()); .build());
} }
@Test
void testGetDomainRenewPrice_oneYear_premiumDomain_default_withToken_isPremiumPrice()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(true)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"premium.example",
clock.nowUtc(),
1,
persistDomainAndSetRecurringBillingEvent(
"premium.example", DEFAULT, Optional.empty()),
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 50).getAmount(), RENEW, true))
.build());
}
@Test
void
testGetDomainRenewPrice_oneYear_premiumDomain_default_withTokenNotValidForPremiums_throwsException()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.build());
assertThrows(
AllocationTokenInvalidForPremiumNameException.class,
() ->
domainPricingLogic.getRenewPrice(
registry,
"premium.example",
clock.nowUtc(),
1,
persistDomainAndSetRecurringBillingEvent(
"premium.example", DEFAULT, Optional.empty()),
Optional.of(allocationToken)));
}
@Test @Test
void testGetDomainRenewPrice_multiYear_premiumDomain_default_isPremiumCost() throws EppException { void testGetDomainRenewPrice_multiYear_premiumDomain_default_isPremiumCost() throws EppException {
assertThat( assertThat(
@ -203,7 +264,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
5, 5,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"premium.example", DEFAULT, Optional.empty()))) "premium.example", DEFAULT, Optional.empty()),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -211,6 +273,60 @@ public class DomainPricingLogicTest {
.build()); .build());
} }
@Test
void testGetDomainRenewPrice_multiYear_premiumDomain_default_withToken_isPremiumPrice()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(true)
.setDiscountYears(2)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"premium.example",
clock.nowUtc(),
5,
persistDomainAndSetRecurringBillingEvent(
"premium.example", DEFAULT, Optional.empty()),
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 400).getAmount(), RENEW, true))
.build());
}
@Test
void
testGetDomainRenewPrice_multiYear_premiumDomain_default_withTokenNotValidForPremiums_throwsException()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.setDiscountYears(2)
.build());
assertThrows(
AllocationTokenInvalidForPremiumNameException.class,
() ->
domainPricingLogic.getRenewPrice(
registry,
"premium.example",
clock.nowUtc(),
5,
persistDomainAndSetRecurringBillingEvent(
"premium.example", DEFAULT, Optional.empty()),
Optional.of(allocationToken)));
}
@Test @Test
void testGetDomainRenewPrice_oneYear_standardDomain_default_isNonPremiumPrice() void testGetDomainRenewPrice_oneYear_standardDomain_default_isNonPremiumPrice()
throws EppException { throws EppException {
@ -221,7 +337,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
1, 1,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"premium.example", DEFAULT, Optional.empty()))) "standard.example", DEFAULT, Optional.empty()),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -229,6 +346,33 @@ public class DomainPricingLogicTest {
.build()); .build());
} }
@Test
void testGetDomainRenewPrice_oneYear_standardDomain_default_withToken_isDiscountedPrice()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"standard.example",
clock.nowUtc(),
1,
persistDomainAndSetRecurringBillingEvent(
"standard.example", DEFAULT, Optional.empty()),
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.build());
}
@Test @Test
void testGetDomainRenewPrice_multiYear_standardDomain_default_isNonPremiumCost() void testGetDomainRenewPrice_multiYear_standardDomain_default_isNonPremiumCost()
throws EppException { throws EppException {
@ -239,7 +383,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
5, 5,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"standard.example", DEFAULT, Optional.empty()))) "standard.example", DEFAULT, Optional.empty()),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -247,6 +392,34 @@ public class DomainPricingLogicTest {
.build()); .build());
} }
@Test
void testGetDomainRenewPrice_multiYear_standardDomain_default_withToken_isDiscountedPrice()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.setDiscountYears(2)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"standard.example",
clock.nowUtc(),
5,
persistDomainAndSetRecurringBillingEvent(
"standard.example", DEFAULT, Optional.empty()),
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 40).getAmount(), RENEW, false))
.build());
}
@Test @Test
void testGetDomainRenewPrice_oneYear_premiumDomain_anchorTenant_isNonPremiumPrice() void testGetDomainRenewPrice_oneYear_premiumDomain_anchorTenant_isNonPremiumPrice()
throws EppException { throws EppException {
@ -257,7 +430,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
1, 1,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"premium.example", NONPREMIUM, Optional.empty()))) "premium.example", NONPREMIUM, Optional.empty()),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -265,6 +439,34 @@ public class DomainPricingLogicTest {
.build()); .build());
} }
@Test
void
testGetDomainRenewPrice_oneYear_premiumDomain_anchorTenant__withToken_isDiscountedNonPremiumPrice()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"premium.example",
clock.nowUtc(),
1,
persistDomainAndSetRecurringBillingEvent(
"premium.example", NONPREMIUM, Optional.empty()),
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.build());
}
@Test @Test
void testGetDomainRenewPrice_multiYear_premiumDomain_anchorTenant_isNonPremiumCost() void testGetDomainRenewPrice_multiYear_premiumDomain_anchorTenant_isNonPremiumCost()
throws EppException { throws EppException {
@ -275,7 +477,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
5, 5,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"premium.example", NONPREMIUM, Optional.empty()))) "premium.example", NONPREMIUM, Optional.empty()),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -283,6 +486,35 @@ public class DomainPricingLogicTest {
.build()); .build());
} }
@Test
void
testGetDomainRenewPrice_multiYear_premiumDomain_anchorTenant__withToken_isDiscountedNonPremiumPrice()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.setDiscountYears(2)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"premium.example",
clock.nowUtc(),
5,
persistDomainAndSetRecurringBillingEvent(
"premium.example", NONPREMIUM, Optional.empty()),
Optional.of(allocationToken)))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 40).getAmount(), RENEW, false))
.build());
}
@Test @Test
void testGetDomainRenewPrice_oneYear_standardDomain_anchorTenant_isNonPremiumPrice() void testGetDomainRenewPrice_oneYear_standardDomain_anchorTenant_isNonPremiumPrice()
throws EppException { throws EppException {
@ -293,7 +525,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
1, 1,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"standard.example", NONPREMIUM, Optional.empty()))) "standard.example", NONPREMIUM, Optional.empty()),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -311,7 +544,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
5, 5,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"standard.example", NONPREMIUM, Optional.empty()))) "standard.example", NONPREMIUM, Optional.empty()),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -329,7 +563,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
1, 1,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"standard.example", SPECIFIED, Optional.of(Money.of(USD, 1))))) "standard.example", SPECIFIED, Optional.of(Money.of(USD, 1))),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -337,6 +572,71 @@ public class DomainPricingLogicTest {
.build()); .build());
} }
@Test
void
testGetDomainRenewPrice_oneYear_standardDomain_internalRegistration_withToken_isSpecifiedPrice()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"standard.example",
clock.nowUtc(),
1,
persistDomainAndSetRecurringBillingEvent(
"standard.example", SPECIFIED, Optional.of(Money.of(USD, 1))),
Optional.of(allocationToken)))
// The allocation token should not discount the speicifed price
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1).getAmount(), RENEW, false))
.build());
}
@Test
void
testGetDomainRenewPrice_oneYear_standardDomain_internalRegistration_withToken_doesNotChangePriceBehavior()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setRenewalPriceBehavior(DEFAULT)
.setDiscountPremiums(false)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"standard.example",
clock.nowUtc(),
1,
persistDomainAndSetRecurringBillingEvent(
"standard.example", SPECIFIED, Optional.of(Money.of(USD, 1))),
Optional.of(allocationToken)))
// The allocation token should not discount the speicifed price
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 1).getAmount(), RENEW, false))
.build());
assertThat(
Iterables.getLast(DatabaseHelper.loadAllOf(BillingEvent.Recurring.class))
.getRenewalPriceBehavior())
.isEqualTo(SPECIFIED);
}
@Test @Test
void testGetDomainRenewPrice_multiYear_standardDomain_internalRegistration_isSpecifiedPrice() void testGetDomainRenewPrice_multiYear_standardDomain_internalRegistration_isSpecifiedPrice()
throws EppException { throws EppException {
@ -347,7 +647,36 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
5, 5,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"standard.example", SPECIFIED, Optional.of(Money.of(USD, 1))))) "standard.example", SPECIFIED, Optional.of(Money.of(USD, 1))),
Optional.empty()))
.isEqualTo(
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(Money.of(USD, 5).getAmount(), RENEW, false))
.build());
}
@Test
void
testGetDomainRenewPrice_multiYear_standardDomain_internalRegistration_withToken_isSpecifiedPrice()
throws EppException {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.build());
assertThat(
domainPricingLogic.getRenewPrice(
registry,
"standard.example",
clock.nowUtc(),
5,
persistDomainAndSetRecurringBillingEvent(
"standard.example", SPECIFIED, Optional.of(Money.of(USD, 1))),
Optional.of(allocationToken)))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -365,7 +694,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
1, 1,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"premium.example", SPECIFIED, Optional.of(Money.of(USD, 17))))) "premium.example", SPECIFIED, Optional.of(Money.of(USD, 17))),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -383,7 +713,8 @@ public class DomainPricingLogicTest {
clock.nowUtc(), clock.nowUtc(),
5, 5,
persistDomainAndSetRecurringBillingEvent( persistDomainAndSetRecurringBillingEvent(
"premium.example", SPECIFIED, Optional.of(Money.of(USD, 17))))) "premium.example", SPECIFIED, Optional.of(Money.of(USD, 17))),
Optional.empty()))
.isEqualTo( .isEqualTo(
new FeesAndCredits.Builder() new FeesAndCredits.Builder()
.setCurrency(USD) .setCurrency(USD)
@ -398,7 +729,7 @@ public class DomainPricingLogicTest {
IllegalArgumentException.class, IllegalArgumentException.class,
() -> () ->
domainPricingLogic.getRenewPrice( domainPricingLogic.getRenewPrice(
registry, "standard.example", clock.nowUtc(), -1, null)); registry, "standard.example", clock.nowUtc(), -1, null, Optional.empty()));
assertThat(thrown).hasMessageThat().isEqualTo("Number of years must be positive"); assertThat(thrown).hasMessageThat().isEqualTo("Number of years must be positive");
} }

View file

@ -49,6 +49,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; 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.truth.Truth8; import com.google.common.truth.Truth8;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.EppRequestSource; import google.registry.flows.EppRequestSource;
@ -604,12 +605,21 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
.setToken("abc123") .setToken("abc123")
.setTokenType(SINGLE_USE) .setTokenType(SINGLE_USE)
.setDomainName("example.tld") .setDomainName("example.tld")
.setDiscountFraction(0.5)
.setDiscountYears(1)
.build()); .build());
runFlowAssertResponse( runFlowAssertResponse(
loadFile( loadFile(
"domain_renew_response.xml", "domain_renew_response.xml",
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00.0Z"))); ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00.0Z")));
assertThat(DatabaseHelper.loadByEntity(allocationToken).getRedemptionHistoryId()).isPresent(); assertThat(DatabaseHelper.loadByEntity(allocationToken).getRedemptionHistoryId()).isPresent();
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey())
.isEqualTo(allocationToken.getToken());
// Price is 50% off the first year only. Non-discounted price is $11.
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 16.5));
} }
@Test @Test
@ -619,11 +629,20 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "abc123")); ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "abc123"));
persistDomain(); persistDomain();
persistResource( persistResource(
new AllocationToken.Builder().setToken("abc123").setTokenType(UNLIMITED_USE).build()); new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setDiscountFraction(0.5)
.build());
runFlowAssertResponse( runFlowAssertResponse(
loadFile( loadFile(
"domain_renew_response.xml", "domain_renew_response.xml",
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00.0Z"))); ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00.0Z")));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo("abc123");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 16.5));
clock.advanceOneMilli(); clock.advanceOneMilli();
setEppInput( setEppInput(
"domain_renew_allocationtoken.xml", "domain_renew_allocationtoken.xml",
@ -633,6 +652,38 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
loadFile( loadFile(
"domain_renew_response.xml", "domain_renew_response.xml",
ImmutableMap.of("DOMAIN", "other-example.tld", "EXDATE", "2002-04-03T22:00:00.0Z"))); ImmutableMap.of("DOMAIN", "other-example.tld", "EXDATE", "2002-04-03T22:00:00.0Z")));
billingEvent = Iterables.getLast(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("other-example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo("abc123");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 16.5));
}
@Test
void testSuccess_allocationTokenMultiYearDiscount() throws Exception {
setEppInput(
"domain_renew_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "abc123"));
persistDomain();
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("example.tld")
.setDiscountFraction(0.5)
.setDiscountYears(10)
.build());
runFlowAssertResponse(
loadFile(
"domain_renew_response.xml",
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00.0Z")));
assertThat(DatabaseHelper.loadByEntity(allocationToken).getRedemptionHistoryId()).isPresent();
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey())
.isEqualTo(allocationToken.getToken());
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 11));
} }
@Test @Test
@ -750,6 +801,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
.setToken("abc123") .setToken("abc123")
.setTokenType(SINGLE_USE) .setTokenType(SINGLE_USE)
.setRedemptionHistoryId(historyEntryId) .setRedemptionHistoryId(historyEntryId)
.setDiscountFraction(0.5)
.build()); .build());
clock.advanceOneMilli(); clock.advanceOneMilli();
EppException thrown = EppException thrown =

View file

@ -24,7 +24,7 @@
<fee:currency>USD</fee:currency> <fee:currency>USD</fee:currency>
<fee:command>renew</fee:command> <fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period> <fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee> <fee:fee description="renew">5.50</fee:fee>
</fee:cd> </fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6"> <fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>example1.tld</fee:name> <fee:name>example1.tld</fee:name>

View file

@ -28,7 +28,7 @@
</fee:object> </fee:object>
<fee:command name="renew"> <fee:command name="renew">
<fee:period unit="y">1</fee:period> <fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee> <fee:fee description="renew">5.50</fee:fee>
</fee:command> </fee:command>
</fee:cd> </fee:cd>
<fee:cd> <fee:cd>