diff --git a/java/google/registry/model/registrar/Registrar.java b/java/google/registry/model/registrar/Registrar.java
index 15d9af040..c38d27950 100644
--- a/java/google/registry/model/registrar/Registrar.java
+++ b/java/google/registry/model/registrar/Registrar.java
@@ -41,6 +41,7 @@ import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
@@ -48,12 +49,15 @@ import com.google.re2j.Pattern;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Work;
import com.googlecode.objectify.annotation.Cache;
+import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
+import com.googlecode.objectify.annotation.Mapify;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.condition.IfNull;
+import com.googlecode.objectify.mapper.Mapper;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
@@ -62,6 +66,7 @@ import google.registry.model.Jsonifiable;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
+import google.registry.model.registrar.Registrar.BillingAccountEntry.CurrencyMapper;
import google.registry.util.CidrAddressBlock;
import google.registry.util.NonFinalForTesting;
import java.security.MessageDigest;
@@ -74,6 +79,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
+import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
/** Information about a registrar. */
@@ -293,6 +299,39 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
/** Identifier of registrar used in external billing system (e.g. Oracle). */
Long billingIdentifier;
+ /**
+ * Map of currency-to-billing account for the registrar.
+ *
+ *
A registrar can have different billing accounts that are denoted in different currencies.
+ * This provides flexibility for billing systems that require such distinction.
+ */
+ @Nullable
+ @Mapify(CurrencyMapper.class)
+ Map billingAccountMap;
+
+ /** A billing account entry for this registrar, consisting of a currency and an account Id. */
+ @Embed
+ static class BillingAccountEntry extends ImmutableObject {
+
+ CurrencyUnit currency;
+ String accountId;
+
+ BillingAccountEntry() {};
+
+ BillingAccountEntry(CurrencyUnit currency, String accountId) {
+ this.accountId = accountId;
+ this.currency = currency;
+ }
+
+ /** Mapper to use for {@code @Mapify}. */
+ static class CurrencyMapper implements Mapper {
+ @Override
+ public CurrencyUnit getKey(BillingAccountEntry billingAccountEntry) {
+ return billingAccountEntry.currency;
+ }
+ }
+ }
+
/** URL of registrar's website. */
String url;
@@ -370,6 +409,18 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
return billingIdentifier;
}
+ public ImmutableMap getBillingAccountMap() {
+ if (billingAccountMap == null) {
+ return ImmutableMap.of();
+ }
+ ImmutableMap.Builder billingAccountMapBuilder =
+ new ImmutableMap.Builder<>();
+ for (Map.Entry entry : billingAccountMap.entrySet()) {
+ billingAccountMapBuilder.put(entry.getKey(), entry.getValue().accountId);
+ }
+ return billingAccountMapBuilder.build();
+ }
+
public DateTime getLastUpdateTime() {
return lastUpdateTime.getTimestamp();
}
@@ -588,6 +639,21 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
return this;
}
+ public Builder setBillingAccountMap(Map billingAccountMap) {
+ if (billingAccountMap == null) {
+ getInstance().billingAccountMap = null;
+ } else {
+ ImmutableMap.Builder billingAccountMapBuilder =
+ new ImmutableMap.Builder<>();
+ for (Map.Entry entry : billingAccountMap.entrySet()) {
+ CurrencyUnit key = entry.getKey();
+ billingAccountMapBuilder.put(key, new BillingAccountEntry(key, entry.getValue()));
+ }
+ getInstance().billingAccountMap = billingAccountMapBuilder.build();
+ }
+ return this;
+ }
+
public Builder setRegistrarName(String registrarName) {
getInstance().registrarName = registrarName;
return this;
diff --git a/java/google/registry/tools/CreateOrUpdateRegistrarCommand.java b/java/google/registry/tools/CreateOrUpdateRegistrarCommand.java
index bfac4097e..db10aab32 100644
--- a/java/google/registry/tools/CreateOrUpdateRegistrarCommand.java
+++ b/java/google/registry/tools/CreateOrUpdateRegistrarCommand.java
@@ -32,6 +32,7 @@ import google.registry.model.billing.RegistrarBillingUtils;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.BillingMethod;
import google.registry.model.registrar.RegistrarAddress;
+import google.registry.tools.params.KeyValueMapParameter.CurrencyUnitToStringMap;
import google.registry.tools.params.OptionalLongParameter;
import google.registry.tools.params.OptionalPhoneNumberParameter;
import google.registry.tools.params.OptionalStringParameter;
@@ -169,6 +170,18 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
validateWith = OptionalLongParameter.class)
private Optional billingId;
+ @Nullable
+ @Parameter(
+ names = "--billing_account_map",
+ description =
+ "Registrar Billing Account key-value pairs (formatted as key=value[,key=value...]), "
+ + "where key is a currency unit (USD, JPY, etc) and value is the registrar's billing "
+ + "account id for that currency.",
+ converter = CurrencyUnitToStringMap.class,
+ validateWith = CurrencyUnitToStringMap.class
+ )
+ private Map billingAccountMap;
+
@Nullable
@Parameter(
names = "--billing_method",
@@ -334,6 +347,9 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
if (billingId != null) {
builder.setBillingIdentifier(billingId.orNull());
}
+ if (billingAccountMap != null) {
+ builder.setBillingAccountMap(billingAccountMap);
+ }
if (billingMethod != null) {
if (oldRegistrar != null && !billingMethod.equals(oldRegistrar.getBillingMethod())) {
Map balances = RegistrarBillingUtils.loadBalance(oldRegistrar);
diff --git a/java/google/registry/tools/params/KeyValueMapParameter.java b/java/google/registry/tools/params/KeyValueMapParameter.java
index 792e84ce9..bf489353b 100644
--- a/java/google/registry/tools/params/KeyValueMapParameter.java
+++ b/java/google/registry/tools/params/KeyValueMapParameter.java
@@ -18,6 +18,7 @@ import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
+import org.joda.money.CurrencyUnit;
/**
* Combined converter and validator class for key-value map JCommander argument strings.
@@ -93,4 +94,17 @@ public abstract class KeyValueMapParameter
return Integer.parseInt(value);
}
}
+
+ /** Combined converter and validator class for currency unit-to-string Map argument strings. */
+ public static class CurrencyUnitToStringMap extends KeyValueMapParameter {
+ @Override
+ protected CurrencyUnit parseKey(String rawKey) {
+ return CurrencyUnit.of(rawKey);
+ }
+
+ @Override
+ protected String parseValue(String value) {
+ return value;
+ }
+ }
}
diff --git a/javatests/google/registry/model/registrar/RegistrarTest.java b/javatests/google/registry/model/registrar/RegistrarTest.java
index 393ca1f0d..a433274af 100644
--- a/javatests/google/registry/model/registrar/RegistrarTest.java
+++ b/javatests/google/registry/model/registrar/RegistrarTest.java
@@ -27,6 +27,7 @@ import static google.registry.testing.DatastoreHelper.persistSimpleResource;
import static google.registry.testing.DatastoreHelper.persistSimpleResources;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.model.EntityTestCase;
import google.registry.model.common.EntityGroupRoot;
@@ -34,6 +35,7 @@ import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.Registrar.Type;
import google.registry.testing.ExceptionRule;
import google.registry.util.CidrAddressBlock;
+import org.joda.money.CurrencyUnit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -50,45 +52,51 @@ public class RegistrarTest extends EntityTestCase {
public void setUp() throws Exception {
createTld("xn--q9jyb4c");
// Set up a new persisted registrar entity.
- registrar = cloneAndSetAutoTimestamps(
- new Registrar.Builder()
- .setClientId("registrar")
- .setRegistrarName("full registrar name")
- .setType(Type.REAL)
- .setState(State.PENDING)
- .setAllowedTlds(ImmutableSet.of("xn--q9jyb4c"))
- .setWhoisServer("whois.example.com")
- .setBlockPremiumNames(true)
- .setClientCertificate(SAMPLE_CERT, clock.nowUtc())
- .setIpAddressWhitelist(ImmutableList.of(
- CidrAddressBlock.create("192.168.1.1/31"),
- CidrAddressBlock.create("10.0.0.1/8")))
- .setPassword("foobar")
- .setInternationalizedAddress(new RegistrarAddress.Builder()
- .setStreet(ImmutableList.of("123 Example Boulevard"))
- .setCity("Williamsburg")
- .setState("NY")
- .setZip("11211")
- .setCountryCode("US")
- .build())
- .setLocalizedAddress(new RegistrarAddress.Builder()
- .setStreet(ImmutableList.of("123 Example Boulevard."))
- .setCity("Williamsburg")
- .setState("NY")
- .setZip("11211")
- .setCountryCode("US")
- .build())
- .setPhoneNumber("+1.2125551212")
- .setFaxNumber("+1.2125551213")
- .setEmailAddress("contact-us@example.com")
- .setUrl("http://www.example.com")
- .setReferralUrl("http://www.example.com")
- .setIcannReferralEmail("foo@example.com")
- .setDriveFolderId("drive folder id")
- .setIanaIdentifier(8L)
- .setBillingIdentifier(5325L)
- .setPhonePasscode("01234")
- .build());
+ registrar =
+ cloneAndSetAutoTimestamps(
+ new Registrar.Builder()
+ .setClientId("registrar")
+ .setRegistrarName("full registrar name")
+ .setType(Type.REAL)
+ .setState(State.PENDING)
+ .setAllowedTlds(ImmutableSet.of("xn--q9jyb4c"))
+ .setWhoisServer("whois.example.com")
+ .setBlockPremiumNames(true)
+ .setClientCertificate(SAMPLE_CERT, clock.nowUtc())
+ .setIpAddressWhitelist(
+ ImmutableList.of(
+ CidrAddressBlock.create("192.168.1.1/31"),
+ CidrAddressBlock.create("10.0.0.1/8")))
+ .setPassword("foobar")
+ .setInternationalizedAddress(
+ new RegistrarAddress.Builder()
+ .setStreet(ImmutableList.of("123 Example Boulevard"))
+ .setCity("Williamsburg")
+ .setState("NY")
+ .setZip("11211")
+ .setCountryCode("US")
+ .build())
+ .setLocalizedAddress(
+ new RegistrarAddress.Builder()
+ .setStreet(ImmutableList.of("123 Example Boulevard."))
+ .setCity("Williamsburg")
+ .setState("NY")
+ .setZip("11211")
+ .setCountryCode("US")
+ .build())
+ .setPhoneNumber("+1.2125551212")
+ .setFaxNumber("+1.2125551213")
+ .setEmailAddress("contact-us@example.com")
+ .setUrl("http://www.example.com")
+ .setReferralUrl("http://www.example.com")
+ .setIcannReferralEmail("foo@example.com")
+ .setDriveFolderId("drive folder id")
+ .setIanaIdentifier(8L)
+ .setBillingIdentifier(5325L)
+ .setBillingAccountMap(
+ ImmutableMap.of(CurrencyUnit.USD, "abc123", CurrencyUnit.JPY, "789xyz"))
+ .setPhonePasscode("01234")
+ .build());
persistResource(registrar);
persistSimpleResources(ImmutableList.of(
new RegistrarContact.Builder()
@@ -225,6 +233,14 @@ public class RegistrarTest extends EntityTestCase {
.build();
}
+ @Test
+ public void testSuccess_clearingBillingAccountMap() throws Exception {
+ registrar = registrar.asBuilder()
+ .setBillingAccountMap(null)
+ .build();
+ assertThat(registrar.getBillingAccountMap()).isEmpty();
+ }
+
@Test
public void testSuccess_ianaIdForInternal() throws Exception {
registrar.asBuilder().setType(Type.INTERNAL).setIanaIdentifier(9998L).build();
diff --git a/javatests/google/registry/model/schema.txt b/javatests/google/registry/model/schema.txt
index 67bd73e86..72a403387 100644
--- a/javatests/google/registry/model/schema.txt
+++ b/javatests/google/registry/model/schema.txt
@@ -617,9 +617,14 @@ class google.registry.model.registrar.Registrar {
java.lang.String url;
java.lang.String whoisServer;
java.util.List ipAddressWhitelist;
+ java.util.Map billingAccountMap;
java.util.Set allowedTlds;
org.joda.time.DateTime lastCertificateUpdateTime;
}
+class google.registry.model.registrar.Registrar$BillingAccountEntry {
+ java.lang.String accountId;
+ org.joda.money.CurrencyUnit currency;
+}
enum google.registry.model.registrar.Registrar$BillingMethod {
BRAINTREE;
EXTERNAL;
diff --git a/javatests/google/registry/tools/CreateRegistrarCommandTest.java b/javatests/google/registry/tools/CreateRegistrarCommandTest.java
index 6482a595b..5aba5d291 100644
--- a/javatests/google/registry/tools/CreateRegistrarCommandTest.java
+++ b/javatests/google/registry/tools/CreateRegistrarCommandTest.java
@@ -34,6 +34,7 @@ import google.registry.model.registrar.Registrar;
import google.registry.testing.CertificateSamples;
import google.registry.tools.ServerSideCommand.Connection;
import java.io.IOException;
+import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
@@ -345,6 +346,25 @@ public class CreateRegistrarCommandTest extends CommandTestCase