mirror of
https://github.com/google/nomulus
synced 2026-10-01 12:16:02 +00:00
Add discount price to AllocationToken (#2559)
* Include discount price in domai n pricing * Partial progress in logic * Tests and logic passing * Change pricing for multi year create * Tests for discount pricing logic * Token currency check * Add some comments * Java formatting * Discount price to Optional * Change discount price to be optional nullable * Re-add deleted tests
This commit is contained in:
@@ -28,6 +28,7 @@ import static google.registry.testing.DatabaseHelper.persistPremiumList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.money.CurrencyUnit.JPY;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@@ -38,6 +39,7 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.HttpSessionMetadata;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.custom.DomainPricingCustomLogic;
|
||||
import google.registry.flows.domain.DomainPricingLogic.AllocationTokenInvalidForCurrencyException;
|
||||
import google.registry.flows.domain.DomainPricingLogic.AllocationTokenInvalidForPremiumNameException;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
|
||||
@@ -46,6 +48,7 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldState;
|
||||
@@ -151,6 +154,92 @@ public class DomainPricingLogicTest {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainCreatePrice_discountPriceAllocationToken_oneYearCreate_appliesDiscount()
|
||||
throws EppException {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDomainName("default.example")
|
||||
.setDiscountPrice(Money.of(USD, 5))
|
||||
.setDiscountYears(1)
|
||||
.setRegistrationBehavior(RegistrationBehavior.DEFAULT)
|
||||
.build());
|
||||
assertThat(
|
||||
domainPricingLogic.getCreatePrice(
|
||||
tld,
|
||||
"default.example",
|
||||
clock.nowUtc(),
|
||||
1,
|
||||
false,
|
||||
false,
|
||||
Optional.of(allocationToken)))
|
||||
.isEqualTo(
|
||||
new FeesAndCredits.Builder()
|
||||
.setCurrency(USD)
|
||||
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), CREATE, false))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainCreatePrice_discountPriceAllocationToken_multiYearCreate_appliesDiscount()
|
||||
throws EppException {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDomainName("default.example")
|
||||
.setDiscountPrice(Money.of(USD, 5))
|
||||
.setDiscountYears(1)
|
||||
.setRegistrationBehavior(RegistrationBehavior.DEFAULT)
|
||||
.build());
|
||||
|
||||
// 3 year create should be 5 (discount price) + 10*2 (regular price) = 25.
|
||||
assertThat(
|
||||
domainPricingLogic.getCreatePrice(
|
||||
tld,
|
||||
"default.example",
|
||||
clock.nowUtc(),
|
||||
3,
|
||||
false,
|
||||
false,
|
||||
Optional.of(allocationToken)))
|
||||
.isEqualTo(
|
||||
new FeesAndCredits.Builder()
|
||||
.setCurrency(USD)
|
||||
.addFeeOrCredit(Fee.create(new BigDecimal("25.00"), CREATE, false))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void
|
||||
testGetDomainCreatePrice_withDiscountPriceToken_domainCurrencyDoesNotMatchTokensCurrency_throwsException() {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDiscountPrice(Money.of(JPY, new BigDecimal("250")))
|
||||
.setDiscountPremiums(false)
|
||||
.build());
|
||||
|
||||
// Domain's currency is not JPY (is USD).
|
||||
assertThrows(
|
||||
AllocationTokenInvalidForCurrencyException.class,
|
||||
() ->
|
||||
domainPricingLogic.getCreatePrice(
|
||||
tld,
|
||||
"default.example",
|
||||
clock.nowUtc(),
|
||||
3,
|
||||
false,
|
||||
false,
|
||||
Optional.of(allocationToken)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainRenewPrice_oneYear_standardDomain_noBilling_isStandardPrice()
|
||||
throws EppException {
|
||||
@@ -269,6 +358,54 @@ public class DomainPricingLogicTest {
|
||||
Optional.of(allocationToken)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void
|
||||
testGetDomainRenewPrice_oneYear_premiumDomain_default_withDiscountPriceToken_throwsException() {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDiscountPrice(Money.of(USD, 5))
|
||||
.setDiscountPremiums(false)
|
||||
.build());
|
||||
assertThrows(
|
||||
AllocationTokenInvalidForPremiumNameException.class,
|
||||
() ->
|
||||
domainPricingLogic.getRenewPrice(
|
||||
tld,
|
||||
"premium.example",
|
||||
clock.nowUtc(),
|
||||
1,
|
||||
persistDomainAndSetRecurrence("premium.example", DEFAULT, Optional.empty()),
|
||||
Optional.of(allocationToken)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void
|
||||
testGetDomainRenewPrice_withDiscountPriceToken_domainCurrencyDoesNotMatchTokensCurrency_throwsException() {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDiscountPrice(Money.of(JPY, new BigDecimal("250")))
|
||||
.setDiscountPremiums(false)
|
||||
.build());
|
||||
|
||||
// Domain's currency is not JPY (is USD).
|
||||
assertThrows(
|
||||
AllocationTokenInvalidForCurrencyException.class,
|
||||
() ->
|
||||
domainPricingLogic.getRenewPrice(
|
||||
tld,
|
||||
"default.example",
|
||||
clock.nowUtc(),
|
||||
1,
|
||||
persistDomainAndSetRecurrence("default.example", DEFAULT, Optional.empty()),
|
||||
Optional.of(allocationToken)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainRenewPrice_multiYear_premiumDomain_default_isPremiumCost() throws EppException {
|
||||
assertThat(
|
||||
@@ -381,6 +518,33 @@ public class DomainPricingLogicTest {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void
|
||||
testGetDomainRenewPrice_oneYear_standardDomain_default_withDiscountPriceToken_isDiscountedPrice()
|
||||
throws EppException {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDiscountPrice(Money.of(USD, 1.5))
|
||||
.setDiscountPremiums(false)
|
||||
.build());
|
||||
assertThat(
|
||||
domainPricingLogic.getRenewPrice(
|
||||
tld,
|
||||
"standard.example",
|
||||
clock.nowUtc(),
|
||||
1,
|
||||
persistDomainAndSetRecurrence("standard.example", DEFAULT, Optional.empty()),
|
||||
Optional.of(allocationToken)))
|
||||
.isEqualTo(
|
||||
new FeesAndCredits.Builder()
|
||||
.setCurrency(USD)
|
||||
.addFeeOrCredit(Fee.create(new BigDecimal("1.50"), RENEW, false))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainRenewPrice_multiYear_standardDomain_default_isNonPremiumCost()
|
||||
throws EppException {
|
||||
@@ -426,6 +590,36 @@ public class DomainPricingLogicTest {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void
|
||||
testGetDomainRenewPrice_multiYear_standardDomain_default_withDiscountPriceToken_isDiscountedPrice()
|
||||
throws EppException {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("discountPrice12345")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDiscountPrice(Money.of(USD, 2.5))
|
||||
.setDiscountPremiums(false)
|
||||
.setDiscountYears(2)
|
||||
.build());
|
||||
|
||||
// 5 year create should be 2*2.5 (discount price) + 10*3 (regular price) = 35.
|
||||
assertThat(
|
||||
domainPricingLogic.getRenewPrice(
|
||||
tld,
|
||||
"standard.example",
|
||||
clock.nowUtc(),
|
||||
5,
|
||||
persistDomainAndSetRecurrence("standard.example", DEFAULT, Optional.empty()),
|
||||
Optional.of(allocationToken)))
|
||||
.isEqualTo(
|
||||
new FeesAndCredits.Builder()
|
||||
.setCurrency(USD)
|
||||
.addFeeOrCredit(Fee.create(new BigDecimal("35.00"), RENEW, false))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainRenewPrice_oneYear_premiumDomain_anchorTenant_isNonPremiumPrice()
|
||||
throws EppException {
|
||||
@@ -602,6 +796,36 @@ public class DomainPricingLogicTest {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void
|
||||
testGetDomainRenewPrice_oneYear_standardDomain_internalRegistration_withDiscountPriceToken_isSpecifiedPrice()
|
||||
throws EppException {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDiscountPrice(Money.of(USD, 0.5))
|
||||
.setDiscountPremiums(false)
|
||||
.build());
|
||||
assertThat(
|
||||
domainPricingLogic.getRenewPrice(
|
||||
tld,
|
||||
"standard.example",
|
||||
clock.nowUtc(),
|
||||
1,
|
||||
persistDomainAndSetRecurrence(
|
||||
"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(new BigDecimal("1.00"), RENEW, false))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void
|
||||
testGetDomainRenewPrice_oneYear_standardDomain_internalRegistration_withToken_doesNotChangePriceBehavior()
|
||||
@@ -684,6 +908,34 @@ public class DomainPricingLogicTest {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void
|
||||
testGetDomainRenewPrice_multiYear_standardDomain_internalRegistration_withDiscountPriceToken_isSpecifiedPrice()
|
||||
throws EppException {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDiscountPrice(Money.of(USD, 0.5))
|
||||
.setDiscountPremiums(false)
|
||||
.build());
|
||||
assertThat(
|
||||
domainPricingLogic.getRenewPrice(
|
||||
tld,
|
||||
"standard.example",
|
||||
clock.nowUtc(),
|
||||
5,
|
||||
persistDomainAndSetRecurrence(
|
||||
"standard.example", SPECIFIED, Optional.of(Money.of(USD, 1))),
|
||||
Optional.of(allocationToken)))
|
||||
.isEqualTo(
|
||||
new FeesAndCredits.Builder()
|
||||
.setCurrency(USD)
|
||||
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), RENEW, false))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainRenewPrice_oneYear_premiumDomain_internalRegistration_isSpecifiedPrice()
|
||||
throws EppException {
|
||||
|
||||
@@ -571,7 +571,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_discountYearsRequiresDiscountFraction() {
|
||||
void testBuild_discountYearsRequiresDiscountFractionOrPrice() {
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
@@ -583,7 +583,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||
.build());
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Discount years can only be specified along with a discount fraction");
|
||||
.isEqualTo("Discount years can only be specified along with a discount fraction/price");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -669,6 +669,42 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_discountPriceCantBeSetWithDiscountFraction() {
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDiscountYears(2)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountPrice(Money.of(CurrencyUnit.USD, 5))
|
||||
.build());
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.isEqualTo("discountFraction and discountPrice can't be set together");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_discountPriceCantBeSetWithNonPremiumCreateRegistrationBehavior() {
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setRegistrationBehavior(RegistrationBehavior.NONPREMIUM_CREATE)
|
||||
.setDiscountYears(2)
|
||||
.setDiscountPrice(Money.of(CurrencyUnit.USD, 5))
|
||||
.build());
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.isEqualTo("NONPREMIUM_CREATE tokens cannot apply a discount");
|
||||
}
|
||||
|
||||
private void assertBadInitialTransition(TokenStatus status) {
|
||||
assertBadTransition(
|
||||
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
|
||||
|
||||
Reference in New Issue
Block a user