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,14 +385,17 @@ 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;
try {
oneTime =
new OneTime.Builder() new OneTime.Builder()
.setBillingTime(billingTime) .setBillingTime(billingTime)
.setRegistrarId(recurring.getRegistrarId()) .setRegistrarId(recurring.getRegistrarId())
// Determine the cost for a one-year renewal. // Determine the cost for a one-year renewal.
.setCost( .setCost(
domainPricingLogic domainPricingLogic
.getRenewPrice(tld, recurring.getTargetId(), eventTime, 1, recurring) .getRenewPrice(
tld, recurring.getTargetId(), eventTime, 1, recurring, Optional.empty())
.getRenewCost()) .getRenewCost())
.setEventTime(eventTime) .setEventTime(eventTime)
.setFlags(union(recurring.getFlags(), Flag.SYNTHETIC)) .setFlags(union(recurring.getFlags(), Flag.SYNTHETIC))
@ -401,6 +406,10 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
.setCancellationMatchingBillingEvent(recurring) .setCancellationMatchingBillingEvent(recurring)
.setTargetId(recurring.getTargetId()) .setTargetId(recurring.getTargetId())
.build(); .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>