google-nomulus/java/google/registry/model/domain/fee/BaseFee.java
jianglai ff221fba96 Validate individual fee types
Currently we validate the fee extension by summing up all fees present in the extension and comparing it against the total fee to be charged. While this works in most cases, we'd like the ability to individually validate each fee. This is especially useful during EAP when two fees are charged, a regular "create" fee that would also be amount we charge during renewal, and a one time "EAP" fee.

Because we can only distinguish fees by their descriptions, we try to match the description to the format string of the fee type enums. We also only require individual fee matches when we are charging more than one type of fees, which makes the change compatible with most existing use cases where only one fees is charged and the description field is ignored in the extension.

We expect the workflow to be that a registrar sends a domain check, and we reply with exactly what fees we are expecting, and then it will use the descriptions in the response to send us a domain create with the correct fees.

Note that we aggregate fees within the same FeeType together. Normally there will only be one fee per type, but in case of custom logic there could be more than one fee for the same type. There is no way to distinguish them as they both use the same description. So it is simpler to just aggregate them.

This CL also includes some reformatting that conforms to google-java-format output.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=186530316
2018-03-06 18:48:39 -05:00

162 lines
4.7 KiB
Java

// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.domain.fee;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Range;
import google.registry.model.ImmutableObject;
import google.registry.xml.PeriodAdapter;
import java.math.BigDecimal;
import java.util.stream.Stream;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.joda.time.DateTime;
import org.joda.time.Period;
/** Base class for the fee and credit types. */
@XmlTransient
public abstract class BaseFee extends ImmutableObject {
/** Enum for when a fee is applied. */
public enum AppliedType {
@XmlEnumValue("immediate")
IMMEDIATE,
@XmlEnumValue("delayed")
DELAYED
}
/** Enum for the type of the fee. */
public enum FeeType {
CREATE("create"),
EAP("Early Access Period, fee expires: %s"),
RENEW("renew"),
RESTORE("restore"),
/**
* A transfer fee.
*
* <p>These are not used by the default system, in which the only fee associated with a transfer
* is the RENEW fee. These exist so that custom pricing logic can create a custom transfer fee
* if desired.
*/
TRANSFER("transfer"),
UPDATE("update"),
CREDIT("%s credit");
private final String formatString;
FeeType(String formatString) {
this.formatString = formatString;
}
String renderDescription(Object... args) {
return String.format(formatString, args);
}
boolean matchFormatString(String description) {
return Ascii.toLowerCase(formatString).contains(Ascii.toLowerCase(description));
}
}
@XmlAttribute String description;
@XmlAttribute AppliedType applied;
@XmlAttribute(name = "grace-period")
@XmlJavaTypeAdapter(PeriodAdapter.class)
Period gracePeriod;
@XmlAttribute Boolean refundable;
@XmlValue BigDecimal cost;
@XmlTransient FeeType type;
@XmlTransient Range<DateTime> validDateRange;
public String getDescription() {
return description;
}
public AppliedType getApplied() {
return firstNonNull(applied, AppliedType.IMMEDIATE);
}
public Period getGracePeriod() {
return firstNonNull(gracePeriod, Period.ZERO);
}
public Boolean getRefundable() {
return firstNonNull(refundable, true);
}
/**
* According to the fee extension specification, a fee must always be non-negative, while a credit
* must always be negative. Essentially, they are the same thing, just with different sign.
* However, we need them to be separate classes for proper JAXB handling.
*
* @see <a href="https://tools.ietf.org/html/draft-brown-epp-fees-03#section-2.4">Registry Fee
* Extension for EPP - Fees and Credits</a>
*/
public BigDecimal getCost() {
return cost;
}
public FeeType getType() {
return type;
}
public boolean hasValidDateRange() {
return validDateRange != null;
}
public Range<DateTime> getValidDateRange() {
checkState(hasValidDateRange());
return validDateRange;
}
public boolean hasZeroCost() {
return cost.signum() == 0;
}
public boolean hasDefaultAttributes() {
return getGracePeriod().equals(Period.ZERO)
&& getApplied().equals(AppliedType.IMMEDIATE)
&& getRefundable();
}
/**
* Parses the description field and returns {@link FeeType}s that match the description.
*
* <p>A {@link FeeType} is a match when its {@code formatString} contains the description, case
* insensitively.
*/
public ImmutableList<FeeType> parseDescriptionForTypes() {
if (description == null) {
return ImmutableList.of();
}
return Stream.of(FeeType.values())
.filter(feeType -> feeType.matchFormatString(description))
.collect(toImmutableList());
}
}