mirror of
https://github.com/google/nomulus.git
synced 2025-08-11 03:59:37 +02:00
Change use of BillingIdentifier to BillingAccountMap in invoicing pipeline (#1577)
* Change billingIdentifier to BillingAccountMap in invoicing pipeline * Add a default for billing account map * Throw error on missing PAK * Add unit test
This commit is contained in:
parent
44ede2b022
commit
41f9f1ef7d
3 changed files with 68 additions and 29 deletions
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
package google.registry.beam.invoicing;
|
package google.registry.beam.invoicing;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static google.registry.beam.BeamUtils.getQueryFromFile;
|
import static google.registry.beam.BeamUtils.getQueryFromFile;
|
||||||
import static org.apache.beam.sdk.values.TypeDescriptors.strings;
|
import static org.apache.beam.sdk.values.TypeDescriptors.strings;
|
||||||
|
@ -53,6 +54,7 @@ import org.apache.beam.sdk.transforms.PTransform;
|
||||||
import org.apache.beam.sdk.values.KV;
|
import org.apache.beam.sdk.values.KV;
|
||||||
import org.apache.beam.sdk.values.PCollection;
|
import org.apache.beam.sdk.values.PCollection;
|
||||||
import org.apache.beam.sdk.values.TypeDescriptor;
|
import org.apache.beam.sdk.values.TypeDescriptor;
|
||||||
|
import org.joda.money.CurrencyUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Definition of a Dataflow Flex pipeline template, which generates a given month's invoices.
|
* Definition of a Dataflow Flex pipeline template, which generates a given month's invoices.
|
||||||
|
@ -122,12 +124,19 @@ public class InvoicingPipeline implements Serializable {
|
||||||
google.registry.model.billing.BillingEvent.OneTime oneTime =
|
google.registry.model.billing.BillingEvent.OneTime oneTime =
|
||||||
(google.registry.model.billing.BillingEvent.OneTime) row[0];
|
(google.registry.model.billing.BillingEvent.OneTime) row[0];
|
||||||
Registrar registrar = (Registrar) row[1];
|
Registrar registrar = (Registrar) row[1];
|
||||||
|
CurrencyUnit currency = oneTime.getCost().getCurrencyUnit();
|
||||||
|
checkState(
|
||||||
|
registrar.getBillingAccountMap().containsKey(currency),
|
||||||
|
"Registrar %s does not have a product account key for the currency unit: %s",
|
||||||
|
registrar.getRegistrarId(),
|
||||||
|
currency);
|
||||||
|
|
||||||
return BillingEvent.create(
|
return BillingEvent.create(
|
||||||
oneTime.getId(),
|
oneTime.getId(),
|
||||||
DateTimeUtils.toZonedDateTime(oneTime.getBillingTime(), ZoneId.of("UTC")),
|
DateTimeUtils.toZonedDateTime(oneTime.getBillingTime(), ZoneId.of("UTC")),
|
||||||
DateTimeUtils.toZonedDateTime(oneTime.getEventTime(), ZoneId.of("UTC")),
|
DateTimeUtils.toZonedDateTime(oneTime.getEventTime(), ZoneId.of("UTC")),
|
||||||
registrar.getRegistrarId(),
|
registrar.getRegistrarId(),
|
||||||
registrar.getBillingIdentifier().toString(),
|
registrar.getBillingAccountMap().get(currency),
|
||||||
registrar.getPoNumber().orElse(""),
|
registrar.getPoNumber().orElse(""),
|
||||||
DomainNameUtils.getTldFromDomainName(oneTime.getTargetId()),
|
DomainNameUtils.getTldFromDomainName(oneTime.getTargetId()),
|
||||||
oneTime.getReason().toString(),
|
oneTime.getReason().toString(),
|
||||||
|
|
|
@ -31,7 +31,7 @@ JOIN Domain d ON b.domainRepoId = d.repoId
|
||||||
JOIN Tld t ON t.tldStrId = d.tld
|
JOIN Tld t ON t.tldStrId = d.tld
|
||||||
LEFT JOIN BillingCancellation c ON b.id = c.refOneTime.billingId
|
LEFT JOIN BillingCancellation c ON b.id = c.refOneTime.billingId
|
||||||
LEFT JOIN BillingCancellation cr ON b.cancellationMatchingBillingEvent = cr.refRecurring.billingId
|
LEFT JOIN BillingCancellation cr ON b.cancellationMatchingBillingEvent = cr.refRecurring.billingId
|
||||||
WHERE r.billingIdentifier IS NOT NULL
|
WHERE r.billingAccountMap IS NOT NULL
|
||||||
AND r.type = 'REAL'
|
AND r.type = 'REAL'
|
||||||
AND t.invoicingEnabled IS TRUE
|
AND t.invoicingEnabled IS TRUE
|
||||||
AND b.billingTime BETWEEN CAST('%FIRST_TIMESTAMP_OF_MONTH%' AS timestamp) AND CAST('%LAST_TIMESTAMP_OF_MONTH%' AS timestamp)
|
AND b.billingTime BETWEEN CAST('%FIRST_TIMESTAMP_OF_MONTH%' AS timestamp) AND CAST('%LAST_TIMESTAMP_OF_MONTH%' AS timestamp)
|
||||||
|
|
|
@ -24,6 +24,10 @@ import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
|
||||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||||
|
import static org.joda.money.CurrencyUnit.CAD;
|
||||||
|
import static org.joda.money.CurrencyUnit.JPY;
|
||||||
|
import static org.joda.money.CurrencyUnit.USD;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
@ -55,6 +59,7 @@ import java.time.ZonedDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import org.apache.beam.sdk.Pipeline.PipelineExecutionException;
|
||||||
import org.apache.beam.sdk.coders.SerializableCoder;
|
import org.apache.beam.sdk.coders.SerializableCoder;
|
||||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||||
import org.apache.beam.sdk.testing.PAssert;
|
import org.apache.beam.sdk.testing.PAssert;
|
||||||
|
@ -294,6 +299,37 @@ class InvoicingPipelineTest {
|
||||||
pipeline.run().waitUntilFinish();
|
pipeline.run().waitUntilFinish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFailure_readFromCloudSqlMissingPAK() throws Exception {
|
||||||
|
Registrar registrar = persistNewRegistrar("TheRegistrar");
|
||||||
|
registrar =
|
||||||
|
registrar
|
||||||
|
.asBuilder()
|
||||||
|
.setBillingAccountMap(ImmutableMap.of(USD, "789"))
|
||||||
|
.setPoNumber(Optional.of("22446688"))
|
||||||
|
.build();
|
||||||
|
persistResource(registrar);
|
||||||
|
Registry test =
|
||||||
|
newRegistry("test", "_TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
|
||||||
|
.asBuilder()
|
||||||
|
.setInvoicingEnabled(true)
|
||||||
|
.build();
|
||||||
|
persistResource(test);
|
||||||
|
DomainBase domain = persistActiveDomain("mycanadiandomain.test");
|
||||||
|
|
||||||
|
persistOneTimeBillingEvent(1, domain, registrar, Reason.RENEW, 3, Money.of(CAD, 20.5));
|
||||||
|
PCollection<BillingEvent> billingEvents = InvoicingPipeline.readFromCloudSql(options, pipeline);
|
||||||
|
billingEvents = billingEvents.apply(new ChangeDomainRepo());
|
||||||
|
PAssert.that(billingEvents).empty();
|
||||||
|
PipelineExecutionException thrown =
|
||||||
|
assertThrows(PipelineExecutionException.class, () -> pipeline.run().waitUntilFinish());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains(
|
||||||
|
"Registrar TheRegistrar does not have a product account key for the currency unit:"
|
||||||
|
+ " CAD");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_saveInvoiceCsv() throws Exception {
|
void testSuccess_saveInvoiceCsv() throws Exception {
|
||||||
InvoicingPipeline.saveInvoiceCsv(billingEvents, options);
|
InvoicingPipeline.saveInvoiceCsv(billingEvents, options);
|
||||||
|
@ -338,7 +374,7 @@ class InvoicingPipelineTest {
|
||||||
+ "LEFT JOIN BillingCancellation c ON b.id = c.refOneTime.billingId\n"
|
+ "LEFT JOIN BillingCancellation c ON b.id = c.refOneTime.billingId\n"
|
||||||
+ "LEFT JOIN BillingCancellation cr ON b.cancellationMatchingBillingEvent ="
|
+ "LEFT JOIN BillingCancellation cr ON b.cancellationMatchingBillingEvent ="
|
||||||
+ " cr.refRecurring.billingId\n"
|
+ " cr.refRecurring.billingId\n"
|
||||||
+ "WHERE r.billingIdentifier IS NOT NULL\n"
|
+ "WHERE r.billingAccountMap IS NOT NULL\n"
|
||||||
+ "AND r.type = 'REAL'\n"
|
+ "AND r.type = 'REAL'\n"
|
||||||
+ "AND t.invoicingEnabled IS TRUE\n"
|
+ "AND t.invoicingEnabled IS TRUE\n"
|
||||||
+ "AND b.billingTime BETWEEN CAST('2017-10-01' AS timestamp) AND CAST('2017-11-01'"
|
+ "AND b.billingTime BETWEEN CAST('2017-10-01' AS timestamp) AND CAST('2017-11-01'"
|
||||||
|
@ -362,18 +398,22 @@ class InvoicingPipelineTest {
|
||||||
persistNewRegistrar("NewRegistrar");
|
persistNewRegistrar("NewRegistrar");
|
||||||
persistNewRegistrar("TheRegistrar");
|
persistNewRegistrar("TheRegistrar");
|
||||||
Registrar registrar1 = persistNewRegistrar("theRegistrar");
|
Registrar registrar1 = persistNewRegistrar("theRegistrar");
|
||||||
registrar1 = registrar1.asBuilder().setBillingIdentifier(234L).build();
|
registrar1 =
|
||||||
|
registrar1
|
||||||
|
.asBuilder()
|
||||||
|
.setBillingAccountMap(ImmutableMap.of(JPY, "234", USD, "234"))
|
||||||
|
.build();
|
||||||
persistResource(registrar1);
|
persistResource(registrar1);
|
||||||
Registrar registrar2 = persistNewRegistrar("bestdomains");
|
Registrar registrar2 = persistNewRegistrar("bestdomains");
|
||||||
registrar2 =
|
registrar2 =
|
||||||
registrar2
|
registrar2
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setBillingIdentifier(456L)
|
.setBillingAccountMap(ImmutableMap.of(USD, "456"))
|
||||||
.setPoNumber(Optional.of("116688"))
|
.setPoNumber(Optional.of("116688"))
|
||||||
.build();
|
.build();
|
||||||
persistResource(registrar2);
|
persistResource(registrar2);
|
||||||
Registrar registrar3 = persistNewRegistrar("anotherRegistrar");
|
Registrar registrar3 = persistNewRegistrar("anotherRegistrar");
|
||||||
registrar3 = registrar3.asBuilder().setBillingIdentifier(789L).build();
|
registrar3 = registrar3.asBuilder().setBillingAccountMap(ImmutableMap.of(USD, "789")).build();
|
||||||
persistResource(registrar3);
|
persistResource(registrar3);
|
||||||
|
|
||||||
Registry test =
|
Registry test =
|
||||||
|
@ -397,10 +437,8 @@ class InvoicingPipelineTest {
|
||||||
DomainBase domain6 = persistActiveDomain("locked.test");
|
DomainBase domain6 = persistActiveDomain("locked.test");
|
||||||
DomainBase domain7 = persistActiveDomain("update-prohibited.test");
|
DomainBase domain7 = persistActiveDomain("update-prohibited.test");
|
||||||
|
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(1, domain1, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||||
1, domain1, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
persistOneTimeBillingEvent(2, domain2, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||||
persistOneTimeBillingEvent(
|
|
||||||
2, domain2, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(
|
||||||
3,
|
3,
|
||||||
domain3,
|
domain3,
|
||||||
|
@ -410,31 +448,27 @@ class InvoicingPipelineTest {
|
||||||
Money.ofMajor(CurrencyUnit.JPY, 70),
|
Money.ofMajor(CurrencyUnit.JPY, 70),
|
||||||
DateTime.parse("2017-09-29T00:00:00.0Z"),
|
DateTime.parse("2017-09-29T00:00:00.0Z"),
|
||||||
DateTime.parse("2017-10-02T00:00:00.0Z"));
|
DateTime.parse("2017-10-02T00:00:00.0Z"));
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(4, domain4, registrar2, Reason.RENEW, 1, Money.of(USD, 20.5));
|
||||||
4, domain4, registrar2, Reason.RENEW, 1, Money.of(CurrencyUnit.USD, 20.5));
|
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(
|
||||||
5,
|
5,
|
||||||
domain5,
|
domain5,
|
||||||
registrar3,
|
registrar3,
|
||||||
Reason.CREATE,
|
Reason.CREATE,
|
||||||
1,
|
1,
|
||||||
Money.of(CurrencyUnit.USD, 0),
|
Money.of(USD, 0),
|
||||||
DateTime.parse("2017-10-04T00:00:00.0Z"),
|
DateTime.parse("2017-10-04T00:00:00.0Z"),
|
||||||
DateTime.parse("2017-10-04T00:00:00.0Z"),
|
DateTime.parse("2017-10-04T00:00:00.0Z"),
|
||||||
Flag.SUNRISE,
|
Flag.SUNRISE,
|
||||||
Flag.ANCHOR_TENANT);
|
Flag.ANCHOR_TENANT);
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(6, domain6, registrar1, Reason.SERVER_STATUS, 0, Money.of(USD, 0));
|
||||||
6, domain6, registrar1, Reason.SERVER_STATUS, 0, Money.of(CurrencyUnit.USD, 0));
|
persistOneTimeBillingEvent(7, domain7, registrar1, Reason.SERVER_STATUS, 0, Money.of(USD, 20));
|
||||||
persistOneTimeBillingEvent(
|
|
||||||
7, domain7, registrar1, Reason.SERVER_STATUS, 0, Money.of(CurrencyUnit.USD, 20));
|
|
||||||
|
|
||||||
// Add billing event for a non-billable registrar
|
// Add billing event for a non-billable registrar
|
||||||
Registrar registrar4 = persistNewRegistrar("noBillRegistrar");
|
Registrar registrar4 = persistNewRegistrar("noBillRegistrar");
|
||||||
registrar4 = registrar4.asBuilder().setBillingIdentifier(null).build();
|
registrar4 = registrar4.asBuilder().setBillingAccountMap(null).build();
|
||||||
persistResource(registrar4);
|
persistResource(registrar4);
|
||||||
DomainBase domain8 = persistActiveDomain("non-billable.test");
|
DomainBase domain8 = persistActiveDomain("non-billable.test");
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(8, domain8, registrar4, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||||
8, domain8, registrar4, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
|
||||||
|
|
||||||
// Add billing event for a non-real registrar
|
// Add billing event for a non-real registrar
|
||||||
Registrar registrar5 = persistNewRegistrar("notRealRegistrar");
|
Registrar registrar5 = persistNewRegistrar("notRealRegistrar");
|
||||||
|
@ -442,19 +476,17 @@ class InvoicingPipelineTest {
|
||||||
registrar5
|
registrar5
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setIanaIdentifier(null)
|
.setIanaIdentifier(null)
|
||||||
.setBillingIdentifier(456L)
|
.setBillingAccountMap(ImmutableMap.of(USD, "456"))
|
||||||
.setType(Registrar.Type.OTE)
|
.setType(Registrar.Type.OTE)
|
||||||
.build();
|
.build();
|
||||||
persistResource(registrar5);
|
persistResource(registrar5);
|
||||||
DomainBase domain9 = persistActiveDomain("not-real.test");
|
DomainBase domain9 = persistActiveDomain("not-real.test");
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(9, domain9, registrar5, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||||
9, domain9, registrar5, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
|
||||||
|
|
||||||
// Add billing event for a non-invoicing TLD
|
// Add billing event for a non-invoicing TLD
|
||||||
createTld("nobill");
|
createTld("nobill");
|
||||||
DomainBase domain10 = persistActiveDomain("test.nobill");
|
DomainBase domain10 = persistActiveDomain("test.nobill");
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(10, domain10, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||||
10, domain10, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
|
||||||
|
|
||||||
// Add billing event before October 2017
|
// Add billing event before October 2017
|
||||||
DomainBase domain11 = persistActiveDomain("july.test");
|
DomainBase domain11 = persistActiveDomain("july.test");
|
||||||
|
@ -471,8 +503,7 @@ class InvoicingPipelineTest {
|
||||||
// Add a billing event with a corresponding cancellation
|
// Add a billing event with a corresponding cancellation
|
||||||
DomainBase domain12 = persistActiveDomain("cancel.test");
|
DomainBase domain12 = persistActiveDomain("cancel.test");
|
||||||
OneTime oneTime =
|
OneTime oneTime =
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(12, domain12, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||||
12, domain12, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
|
||||||
DomainHistory domainHistory = persistDomainHistory(domain12, registrar1);
|
DomainHistory domainHistory = persistDomainHistory(domain12, registrar1);
|
||||||
|
|
||||||
Cancellation cancellation =
|
Cancellation cancellation =
|
||||||
|
@ -507,8 +538,7 @@ class InvoicingPipelineTest {
|
||||||
.build();
|
.build();
|
||||||
persistResource(recurring);
|
persistResource(recurring);
|
||||||
OneTime oneTimeRecurring =
|
OneTime oneTimeRecurring =
|
||||||
persistOneTimeBillingEvent(
|
persistOneTimeBillingEvent(13, domain13, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||||
13, domain13, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
|
||||||
oneTimeRecurring =
|
oneTimeRecurring =
|
||||||
oneTimeRecurring
|
oneTimeRecurring
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue