From 511e8baec3dba8b6b5f7d9036408a99a46930bd6 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 05:03:05 -0700 Subject: [PATCH 001/137] jaxrs: add support for HPP combo call Signed-off-by: Pierre-Alexandre Meyer --- .../json/ComboHostedPaymentPageJson.java | 76 +++++++++++ .../billing/jaxrs/json/ComboPaymentJson.java | 93 +++++++++++++ .../json/ComboPaymentTransactionJson.java | 58 ++------ .../jaxrs/resources/ComboPaymentResource.java | 127 ++++++++++++++++++ .../jaxrs/resources/JaxRsResourceBase.java | 15 +++ .../resources/PaymentGatewayResource.java | 63 +++++---- .../jaxrs/resources/PaymentResource.java | 99 +------------- .../payment/core/PaymentGatewayProcessor.java | 6 +- .../DefaultNoOpGatewayNotification.java | 56 ++++++++ ...ltNoOpHostedPaymentPageFormDescriptor.java | 87 ++++++++++++ .../ExternalPaymentProviderPlugin.java | 8 +- .../provider/MockPaymentProviderPlugin.java | 8 +- pom.xml | 2 +- .../billing/jaxrs/TestPaymentGateway.java | 70 ++++++++++ 14 files changed, 594 insertions(+), 174 deletions(-) create mode 100644 jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboHostedPaymentPageJson.java create mode 100644 jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentJson.java create mode 100644 jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java create mode 100644 payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpGatewayNotification.java create mode 100644 payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpHostedPaymentPageFormDescriptor.java create mode 100644 profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboHostedPaymentPageJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboHostedPaymentPageJson.java new file mode 100644 index 0000000000..8e72c5f5ff --- /dev/null +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboHostedPaymentPageJson.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Groupon, Inc + * Copyright 2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs.json; + +import java.util.List; + +import javax.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ComboHostedPaymentPageJson extends ComboPaymentJson { + + private final HostedPaymentPageFieldsJson hostedPaymentPageFields; + + @JsonCreator + public ComboHostedPaymentPageJson(@JsonProperty("account") final AccountJson account, + @JsonProperty("paymentMethod") final PaymentMethodJson paymentMethod, + @JsonProperty("hostedPaymentPageFields") final HostedPaymentPageFieldsJson hostedPaymentPageFields, + @JsonProperty("paymentMethodPluginProperties") final Iterable paymentMethodPluginProperties, + @JsonProperty("auditLogs") @Nullable final List auditLogs) { + super(account, paymentMethod, paymentMethodPluginProperties, auditLogs); + this.hostedPaymentPageFields = hostedPaymentPageFields; + } + + public HostedPaymentPageFieldsJson getHostedPaymentPageFieldsJson() { + return hostedPaymentPageFields; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ComboHostedPaymentPageJson{"); + sb.append("hostedPaymentPageFields=").append(hostedPaymentPageFields); + sb.append('}'); + return sb.toString(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + final ComboHostedPaymentPageJson that = (ComboHostedPaymentPageJson) o; + + return !(hostedPaymentPageFields != null ? !hostedPaymentPageFields.equals(that.hostedPaymentPageFields) : that.hostedPaymentPageFields != null); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (hostedPaymentPageFields != null ? hostedPaymentPageFields.hashCode() : 0); + return result; + } +} diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentJson.java new file mode 100644 index 0000000000..be99ecaf46 --- /dev/null +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentJson.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015 Groupon, Inc + * Copyright 2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs.json; + +import java.util.List; + +import javax.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public abstract class ComboPaymentJson extends JsonBase { + + private final AccountJson account; + private final PaymentMethodJson paymentMethod; + private final Iterable paymentMethodPluginProperties; + + @JsonCreator + public ComboPaymentJson(@JsonProperty("account") final AccountJson account, + @JsonProperty("paymentMethod") final PaymentMethodJson paymentMethod, + @JsonProperty("paymentMethodPluginProperties") final Iterable paymentMethodPluginProperties, + @JsonProperty("auditLogs") @Nullable final List auditLogs) { + super(auditLogs); + this.account = account; + this.paymentMethod = paymentMethod; + this.paymentMethodPluginProperties = paymentMethodPluginProperties; + } + + public AccountJson getAccount() { + return account; + } + + public PaymentMethodJson getPaymentMethod() { + return paymentMethod; + } + + public Iterable getPaymentMethodPluginProperties() { + return paymentMethodPluginProperties; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ComboPaymentJson{"); + sb.append("account=").append(account); + sb.append(", paymentMethod=").append(paymentMethod); + sb.append(", paymentMethodPluginProperties=").append(paymentMethodPluginProperties); + sb.append('}'); + return sb.toString(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ComboPaymentJson that = (ComboPaymentJson) o; + + if (account != null ? !account.equals(that.account) : that.account != null) { + return false; + } + if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) { + return false; + } + return !(paymentMethodPluginProperties != null ? !paymentMethodPluginProperties.equals(that.paymentMethodPluginProperties) : that.paymentMethodPluginProperties != null); + } + + @Override + public int hashCode() { + int result = account != null ? account.hashCode() : 0; + result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0); + result = 31 * result + (paymentMethodPluginProperties != null ? paymentMethodPluginProperties.hashCode() : 0); + return result; + } +} diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentTransactionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentTransactionJson.java index 9a5074167f..3ca1eadfd8 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentTransactionJson.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentTransactionJson.java @@ -1,6 +1,6 @@ /* - * Copyright 2014-2015 Groupon, Inc - * Copyright 2014-2015 The Billing Project, LLC + * Copyright 2015 Groupon, Inc + * Copyright 2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -24,12 +24,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -public class ComboPaymentTransactionJson extends JsonBase { +public class ComboPaymentTransactionJson extends ComboPaymentJson { - private final AccountJson account; - private final PaymentMethodJson paymentMethod; private final PaymentTransactionJson transaction; - private final Iterable paymentMethodPluginProperties; private final Iterable transactionPluginProperties; @JsonCreator @@ -39,44 +36,26 @@ public ComboPaymentTransactionJson(@JsonProperty("account") final AccountJson ac @JsonProperty("paymentMethodPluginProperties") final Iterable paymentMethodPluginProperties, @JsonProperty("transactionPluginProperties") final Iterable transactionPluginProperties, @JsonProperty("auditLogs") @Nullable final List auditLogs) { - super(auditLogs); - this.account = account; - this.paymentMethod = paymentMethod; + super(account, paymentMethod, paymentMethodPluginProperties, auditLogs); this.transaction = transaction; - this.paymentMethodPluginProperties = paymentMethodPluginProperties; this.transactionPluginProperties = transactionPluginProperties; } - - public AccountJson getAccount() { - return account; - } - - public PaymentMethodJson getPaymentMethod() { - return paymentMethod; - } - public PaymentTransactionJson getTransaction() { return transaction; } - public Iterable getPaymentMethodPluginProperties() { - return paymentMethodPluginProperties; - } - public Iterable getTransactionPluginProperties() { return transactionPluginProperties; } @Override public String toString() { - return "ComboPaymentTransactionJson{" + - "account=" + account + - ", paymentMethod=" + paymentMethod + - ", transaction=" + transaction + - ", paymentMethodPluginProperties=" + paymentMethodPluginProperties + - ", transactionPluginProperties=" + transactionPluginProperties + - '}'; + final StringBuilder sb = new StringBuilder("ComboPaymentTransactionJson{"); + sb.append("transaction=").append(transaction); + sb.append(", transactionPluginProperties=").append(transactionPluginProperties); + sb.append('}'); + return sb.toString(); } @Override @@ -84,34 +63,25 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (!(o instanceof ComboPaymentTransactionJson)) { + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { return false; } final ComboPaymentTransactionJson that = (ComboPaymentTransactionJson) o; - if (account != null ? !account.equals(that.account) : that.account != null) { - return false; - } - if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) { - return false; - } if (transaction != null ? !transaction.equals(that.transaction) : that.transaction != null) { return false; } - if (paymentMethodPluginProperties != null ? !paymentMethodPluginProperties.equals(that.paymentMethodPluginProperties) : that.paymentMethodPluginProperties != null) { - return false; - } return !(transactionPluginProperties != null ? !transactionPluginProperties.equals(that.transactionPluginProperties) : that.transactionPluginProperties != null); - } @Override public int hashCode() { - int result = account != null ? account.hashCode() : 0; - result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0); + int result = super.hashCode(); result = 31 * result + (transaction != null ? transaction.hashCode() : 0); - result = 31 * result + (paymentMethodPluginProperties != null ? paymentMethodPluginProperties.hashCode() : 0); result = 31 * result + (transactionPluginProperties != null ? transactionPluginProperties.hashCode() : 0); return result; } diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java new file mode 100644 index 0000000000..a372150c52 --- /dev/null +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java @@ -0,0 +1,127 @@ +/* + * Copyright 2015 Groupon, Inc + * Copyright 2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs.resources; + +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.AccountApiException; +import org.killbill.billing.account.api.AccountUserApi; +import org.killbill.billing.jaxrs.json.AccountJson; +import org.killbill.billing.jaxrs.json.PaymentMethodJson; +import org.killbill.billing.jaxrs.util.Context; +import org.killbill.billing.jaxrs.util.JaxrsUriBuilder; +import org.killbill.billing.payment.api.Payment; +import org.killbill.billing.payment.api.PaymentApi; +import org.killbill.billing.payment.api.PaymentApiException; +import org.killbill.billing.payment.api.PaymentMethod; +import org.killbill.billing.payment.api.PluginProperty; +import org.killbill.billing.util.api.AuditUserApi; +import org.killbill.billing.util.api.CustomFieldUserApi; +import org.killbill.billing.util.api.TagUserApi; +import org.killbill.billing.util.callcontext.CallContext; +import org.killbill.billing.util.callcontext.TenantContext; +import org.killbill.clock.Clock; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +public abstract class ComboPaymentResource extends JaxRsResourceBase { + + @Inject + public ComboPaymentResource(final JaxrsUriBuilder uriBuilder, + final TagUserApi tagUserApi, + final CustomFieldUserApi customFieldUserApi, + final AuditUserApi auditUserApi, + final AccountUserApi accountUserApi, + final PaymentApi paymentApi, + final Clock clock, + final Context context) { + super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context); + } + + protected Account getOrCreateAccount(final AccountJson accountJson, final CallContext callContext) throws AccountApiException { + // Attempt to retrieve by accountId if specified + if (accountJson.getAccountId() != null) { + return accountUserApi.getAccountById(UUID.fromString(accountJson.getAccountId()), callContext); + } + + if (accountJson.getExternalKey() != null) { + // Attempt to retrieve by account externalKey, ignore if does not exist so we can create it with the key specified. + try { + return accountUserApi.getAccountByKey(accountJson.getExternalKey(), callContext); + } catch (final AccountApiException ignore) { + } + } + // Finally create if does not exist + return accountUserApi.createAccount(accountJson.toAccountData(), callContext); + } + + protected UUID getOrCreatePaymentMethod(final Account account, final PaymentMethodJson paymentMethodJson, final Iterable pluginProperties, final CallContext callContext) throws PaymentApiException { + // Get all payment methods for account + final List accountPaymentMethods = paymentApi.getAccountPaymentMethods(account.getId(), false, ImmutableList.of(), callContext); + + // If we were specified a paymentMethod id and we find it, we return it + if (paymentMethodJson.getPaymentMethodId() != null) { + final UUID match = UUID.fromString(paymentMethodJson.getPaymentMethodId()); + if (Iterables.any(accountPaymentMethods, new Predicate() { + @Override + public boolean apply(final PaymentMethod input) { + return input.getId().equals(match); + } + })) { + return match; + } + } + + // If we were specified a paymentMethod externalKey and we find it, we return it + if (paymentMethodJson.getExternalKey() != null) { + final PaymentMethod match = Iterables.tryFind(accountPaymentMethods, new Predicate() { + @Override + public boolean apply(final PaymentMethod input) { + return input.getExternalKey().equals(paymentMethodJson.getExternalKey()); + } + }).orNull(); + if (match != null) { + return match.getId(); + } + } + + // Only set as default if this is the first paymentMethod on the account + final boolean isDefault = accountPaymentMethods.isEmpty(); + final PaymentMethod paymentData = paymentMethodJson.toPaymentMethod(account.getId().toString()); + return paymentApi.addPaymentMethod(account, paymentMethodJson.getExternalKey(), paymentMethodJson.getPluginName(), isDefault, + paymentData.getPluginDetail(), pluginProperties, callContext); + } + + protected Payment getPaymentByIdOrKey(@Nullable final String paymentIdStr, @Nullable final String externalKey, final Iterable pluginProperties, final TenantContext tenantContext) throws PaymentApiException { + Preconditions.checkArgument(paymentIdStr != null || externalKey != null, "Need to set either paymentId or payment externalKey"); + if (paymentIdStr != null) { + final UUID paymentId = UUID.fromString(paymentIdStr); + return paymentApi.getPayment(paymentId, false, pluginProperties, tenantContext); + } else { + return paymentApi.getPaymentByExternalKey(externalKey, false, pluginProperties, tenantContext); + } + } +} diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java index 06581fcdfb..29218826ec 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java @@ -54,6 +54,7 @@ import org.killbill.billing.invoice.api.InvoicePaymentType; import org.killbill.billing.jaxrs.json.CustomFieldJson; import org.killbill.billing.jaxrs.json.JsonBase; +import org.killbill.billing.jaxrs.json.PluginPropertyJson; import org.killbill.billing.jaxrs.json.TagJson; import org.killbill.billing.jaxrs.util.Context; import org.killbill.billing.jaxrs.util.JaxrsUriBuilder; @@ -340,6 +341,20 @@ private LocalDate extractLocalDate(final String inputDate) { return null; } + protected Iterable extractPluginProperties(@Nullable final Iterable pluginProperties) { + return pluginProperties != null ? + Iterables.transform(pluginProperties, + new Function() { + @Override + public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) { + return pluginPropertyJson.toPluginProperty(); + } + } + ) : + ImmutableList.of(); + + } + protected Iterable extractPluginProperties(@Nullable final Iterable pluginProperties, final PluginProperty... additionalProperties) { final Collection properties = new LinkedList(); if (pluginProperties == null) { diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java index 609ef2d53c..7f50aa1ad4 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java @@ -1,6 +1,6 @@ /* - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -32,20 +32,18 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountUserApi; +import org.killbill.billing.jaxrs.json.ComboHostedPaymentPageJson; import org.killbill.billing.jaxrs.json.GatewayNotificationJson; import org.killbill.billing.jaxrs.json.HostedPaymentPageFieldsJson; import org.killbill.billing.jaxrs.json.HostedPaymentPageFormDescriptorJson; -import org.killbill.billing.jaxrs.json.PluginPropertyJson; import org.killbill.billing.jaxrs.util.Context; import org.killbill.billing.jaxrs.util.JaxrsUriBuilder; import org.killbill.billing.payment.api.PaymentApi; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.api.PaymentGatewayApi; -import org.killbill.billing.payment.api.PaymentMethod; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.plugin.api.GatewayNotification; import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor; @@ -56,10 +54,7 @@ import org.killbill.clock.Clock; import com.codahale.metrics.annotation.Timed; -import com.google.common.base.Function; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.inject.Singleton; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; @@ -72,7 +67,7 @@ @Singleton @Path(JaxrsResource.PAYMENT_GATEWAYS_PATH) @Api(value = JaxrsResource.PAYMENT_GATEWAYS_PATH, description = "HPP endpoints") -public class PaymentGatewayResource extends JaxRsResourceBase { +public class PaymentGatewayResource extends ComboPaymentResource { private final PaymentGatewayApi paymentGatewayApi; @@ -90,6 +85,40 @@ public PaymentGatewayResource(final JaxrsUriBuilder uriBuilder, this.paymentGatewayApi = paymentGatewayApi; } + @Timed + @POST + @Path("/" + HOSTED + "/" + FORM) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Combo API to generate form data to redirect the customer to the gateway", response = HostedPaymentPageFormDescriptorJson.class) + @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid data for Account or PaymentMethod")}) + public Response buildComboFormDescriptor(final ComboHostedPaymentPageJson json, + @PathParam("accountId") final String accountIdString, + @QueryParam(QUERY_PLUGIN_PROPERTY) final List pluginPropertiesString, + @HeaderParam(HDR_CREATED_BY) final String createdBy, + @HeaderParam(HDR_REASON) final String reason, + @HeaderParam(HDR_COMMENT) final String comment, + @javax.ws.rs.core.Context final UriInfo uriInfo, + @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException { + verifyNonNullOrEmpty(json, "ComboHostedPaymentPageJson body should be specified"); + + final Iterable pluginProperties = extractPluginProperties(pluginPropertiesString); + + final CallContext callContext = context.createContext(createdBy, reason, comment, request); + final Account account = getOrCreateAccount(json.getAccount(), callContext); + + final Iterable paymentMethodPluginProperties = extractPluginProperties(json.getPaymentMethodPluginProperties()); + final UUID paymentMethodId = getOrCreatePaymentMethod(account, json.getPaymentMethod(), paymentMethodPluginProperties, callContext); + + final HostedPaymentPageFieldsJson hostedPaymentPageFields = json.getHostedPaymentPageFieldsJson(); + final Iterable customFields = extractPluginProperties(hostedPaymentPageFields != null ? hostedPaymentPageFields.getCustomFields() : null); + + final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, paymentMethodId, customFields, pluginProperties, callContext); + final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor); + + return Response.status(Response.Status.OK).entity(result).build(); + } + @Timed @POST @Path("/" + HOSTED + "/" + FORM + "/{" + QUERY_ACCOUNT_ID + ":" + UUID_PATTERN + "}") @@ -115,26 +144,14 @@ public Response buildFormDescriptor(final HostedPaymentPageFieldsJson json, validatePaymentMethodForAccount(accountId, paymentMethodId, callContext); - final Iterable customFields; - if (json == null) { - customFields = ImmutableList.of(); - } else { - customFields = Iterables.transform(json.getCustomFields(), - new Function() { - @Override - public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) { - return pluginPropertyJson.toPluginProperty(); - } - } - ); - } + final Iterable customFields = extractPluginProperties(json.getCustomFields()); + final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, paymentMethodId, customFields, pluginProperties, callContext); final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor); return Response.status(Response.Status.OK).entity(result).build(); } - @Timed @POST @Path("/" + NOTIFICATION + "/{" + QUERY_PAYMENT_PLUGIN_NAME + ":" + ANYTHING_PATTERN + "}") diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java index 42b942f811..1122a21fb1 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java @@ -46,18 +46,14 @@ import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountUserApi; import org.killbill.billing.catalog.api.Currency; -import org.killbill.billing.jaxrs.json.AccountJson; import org.killbill.billing.jaxrs.json.ComboPaymentTransactionJson; import org.killbill.billing.jaxrs.json.PaymentJson; -import org.killbill.billing.jaxrs.json.PaymentMethodJson; import org.killbill.billing.jaxrs.json.PaymentTransactionJson; -import org.killbill.billing.jaxrs.json.PluginPropertyJson; import org.killbill.billing.jaxrs.util.Context; import org.killbill.billing.jaxrs.util.JaxrsUriBuilder; import org.killbill.billing.payment.api.Payment; import org.killbill.billing.payment.api.PaymentApi; import org.killbill.billing.payment.api.PaymentApiException; -import org.killbill.billing.payment.api.PaymentMethod; import org.killbill.billing.payment.api.PaymentOptions; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionType; @@ -72,12 +68,8 @@ import com.codahale.metrics.annotation.Timed; import com.google.common.base.Function; -import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiResponse; @@ -87,7 +79,7 @@ @Path(JaxrsResource.PAYMENTS_PATH) @Api(value = JaxrsResource.PAYMENTS_PATH, description = "Operations on payments") -public class PaymentResource extends JaxRsResourceBase { +public class PaymentResource extends ComboPaymentResource { @Inject public PaymentResource(final JaxrsUriBuilder uriBuilder, @@ -484,17 +476,7 @@ public Response createComboPayment(final ComboPaymentTransactionJson json, final CallContext callContext = context.createContext(createdBy, reason, comment, request); final Account account = getOrCreateAccount(json.getAccount(), callContext); - final Iterable paymentMethodPluginProperties = json.getPaymentMethodPluginProperties() != null ? - Iterables.transform(json.getPaymentMethodPluginProperties(), - new Function() { - @Override - public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) { - return pluginPropertyJson.toPluginProperty(); - } - } - ) : - ImmutableList.of(); - + final Iterable paymentMethodPluginProperties = extractPluginProperties(json.getPaymentMethodPluginProperties()); final UUID paymentMethodId = getOrCreatePaymentMethod(account, json.getPaymentMethod(), paymentMethodPluginProperties, callContext); final PaymentTransactionJson paymentTransactionJson = json.getTransaction(); @@ -502,16 +484,7 @@ public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) { final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames); final Payment result; - final Iterable transactionPluginProperties = json.getTransactionPluginProperties() != null ? - Iterables.transform(json.getTransactionPluginProperties(), - new Function() { - @Override - public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) { - return pluginPropertyJson.toPluginProperty(); - } - } - ) : - ImmutableList.of(); + final Iterable transactionPluginProperties = extractPluginProperties(json.getTransactionPluginProperties()); final Currency currency = paymentTransactionJson.getCurrency() == null ? account.getCurrency() : Currency.valueOf(paymentTransactionJson.getCurrency()); final UUID paymentId = null; // If we need to specify a paymentId (e.g 3DS authorization, we can use regular API, no need for combo call) @@ -541,70 +514,4 @@ public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) { protected ObjectType getObjectType() { return ObjectType.PAYMENT; } - - private Account getOrCreateAccount(final AccountJson accountJson, final CallContext callContext) throws AccountApiException { - // Attempt to retrieve by accountId if specified - if (accountJson.getAccountId() != null) { - return accountUserApi.getAccountById(UUID.fromString(accountJson.getAccountId()), callContext); - } - - if (accountJson.getExternalKey() != null) { - // Attempt to retrieve by account externalKey, ignore if does not exist so we can create it with the key specified. - try { - return accountUserApi.getAccountByKey(accountJson.getExternalKey(), callContext); - } catch (final AccountApiException ignore) {} - } - // Finally create if does not exist - return accountUserApi.createAccount(accountJson.toAccountData(), callContext); - } - - private UUID getOrCreatePaymentMethod(final Account account, final PaymentMethodJson paymentMethodJson, final Iterable pluginProperties, final CallContext callContext) throws PaymentApiException { - - // Get all payment methods for account - final List accountPaymentMethods = paymentApi.getAccountPaymentMethods(account.getId(), false, ImmutableList.of(), callContext); - - // If we were specified a paymentMethod id and we find it, we return it - if (paymentMethodJson.getPaymentMethodId() != null) { - final UUID match = UUID.fromString(paymentMethodJson.getPaymentMethodId()); - if (Iterables.any(accountPaymentMethods, new Predicate() { - @Override - public boolean apply(final PaymentMethod input) { - return input.getId().equals(match); - } - })) { - return match; - } - } - - // If we were specified a paymentMethod externalKey and we find it, we return it - if (paymentMethodJson.getExternalKey() != null) { - final PaymentMethod match = Iterables.tryFind(accountPaymentMethods, new Predicate() { - @Override - public boolean apply(final PaymentMethod input) { - return input.getExternalKey().equals(paymentMethodJson.getExternalKey()); - } - }).orNull(); - if (match != null) { - return match.getId(); - } - } - - // Only set as default if this is the first paymentMethod on the account - final boolean isDefault = accountPaymentMethods.isEmpty(); - final PaymentMethod paymentData = paymentMethodJson.toPaymentMethod(account.getId().toString()); - return paymentApi.addPaymentMethod(account, paymentMethodJson.getExternalKey(), paymentMethodJson.getPluginName(), isDefault, - paymentData.getPluginDetail(), pluginProperties, callContext); - } - - private Payment getPaymentByIdOrKey(@Nullable final String paymentIdStr, @Nullable final String externalKey, final Iterable pluginProperties, final TenantContext tenantContext) throws PaymentApiException { - - Preconditions.checkArgument(paymentIdStr != null || externalKey != null, "Need to set either paymentId or payment externalKey"); - if (paymentIdStr != null) { - final UUID paymentId = UUID.fromString(paymentIdStr); - return paymentApi.getPayment(paymentId, false, pluginProperties, tenantContext); - } else { - return paymentApi.getPaymentByExternalKey(externalKey, false, pluginProperties, tenantContext); - } - } - } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java index c142f184b3..dbc75412be 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java @@ -39,6 +39,8 @@ import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor; import org.killbill.billing.payment.plugin.api.PaymentPluginApi; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; +import org.killbill.billing.payment.provider.DefaultNoOpGatewayNotification; +import org.killbill.billing.payment.provider.DefaultNoOpHostedPaymentPageFormDescriptor; import org.killbill.billing.tag.TagInternalApi; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.InternalCallContextFactory; @@ -90,7 +92,7 @@ public PluginDispatcherReturnType call() throws PaymentApiE final PaymentPluginApi plugin = getPaymentPluginApi(pluginName); try { final GatewayNotification result = plugin.processNotification(notification, properties, callContext); - return PluginDispatcher.createPluginDispatcherReturnType(result); + return PluginDispatcher.createPluginDispatcherReturnType(result == null ? new DefaultNoOpGatewayNotification() : result); } catch (final PaymentPluginApiException e) { throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage()); } @@ -107,7 +109,7 @@ public PluginDispatcherReturnType call() throws try { final HostedPaymentPageFormDescriptor result = plugin.buildFormDescriptor(account.getId(), customFields, properties, callContext); - return PluginDispatcher.createPluginDispatcherReturnType(result); + return PluginDispatcher.createPluginDispatcherReturnType(result == null ? new DefaultNoOpHostedPaymentPageFormDescriptor(account.getId()) : result); } catch (final RuntimeException e) { throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), "")); } catch (final PaymentPluginApiException e) { diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpGatewayNotification.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpGatewayNotification.java new file mode 100644 index 0000000000..e377fab9e0 --- /dev/null +++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpGatewayNotification.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Groupon, Inc + * Copyright 2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.payment.provider; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.killbill.billing.payment.api.PluginProperty; +import org.killbill.billing.payment.plugin.api.GatewayNotification; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class DefaultNoOpGatewayNotification implements GatewayNotification { + + @Override + public UUID getKbPaymentId() { + return null; + } + + @Override + public int getStatus() { + return 200; + } + + @Override + public String getEntity() { + return null; + } + + @Override + public Map> getHeaders() { + return ImmutableMap.>of(); + } + + @Override + public List getProperties() { + return ImmutableList.of(); + } +} diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpHostedPaymentPageFormDescriptor.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpHostedPaymentPageFormDescriptor.java new file mode 100644 index 0000000000..f623aaf973 --- /dev/null +++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpHostedPaymentPageFormDescriptor.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015 Groupon, Inc + * Copyright 2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.payment.provider; + +import java.util.List; +import java.util.UUID; + +import org.killbill.billing.payment.api.PluginProperty; +import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor; + +import com.google.common.collect.ImmutableList; + +public class DefaultNoOpHostedPaymentPageFormDescriptor implements HostedPaymentPageFormDescriptor { + + private final UUID kbAccountId; + + public DefaultNoOpHostedPaymentPageFormDescriptor(final UUID kbAccountId) { + this.kbAccountId = kbAccountId; + } + + @Override + public UUID getKbAccountId() { + return kbAccountId; + } + + @Override + public String getFormMethod() { + return null; + } + + @Override + public String getFormUrl() { + return null; + } + + @Override + public List getFormFields() { + return ImmutableList.of(); + } + + @Override + public List getProperties() { + return ImmutableList.of(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("DefaultNoOpHostedPaymentPageFormDescriptor{"); + sb.append("kbAccountId=").append(kbAccountId); + sb.append('}'); + return sb.toString(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final DefaultNoOpHostedPaymentPageFormDescriptor that = (DefaultNoOpHostedPaymentPageFormDescriptor) o; + + return !(kbAccountId != null ? !kbAccountId.equals(that.kbAccountId) : that.kbAccountId != null); + } + + @Override + public int hashCode() { + return kbAccountId != null ? kbAccountId.hashCode() : 0; + } +} diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java index a6167a82e4..788bb2816c 100644 --- a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java +++ b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java @@ -1,7 +1,7 @@ /* * Copyright 2010-2013 Ning, Inc. - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -135,11 +135,11 @@ public void resetPaymentMethods(final UUID kbAccountId, final List customFields, final Iterable properties, final CallContext callContext) { - return null; + return new DefaultNoOpHostedPaymentPageFormDescriptor(kbAccountId); } @Override public GatewayNotification processNotification(final String notification, final Iterable properties, final CallContext callContext) throws PaymentPluginApiException { - return null; + return new DefaultNoOpGatewayNotification(); } } diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java index f161297417..0d3e7ce0be 100644 --- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java +++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java @@ -1,7 +1,7 @@ /* * Copyright 2010-2013 Ning, Inc. - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -305,12 +305,12 @@ public void resetPaymentMethods(final UUID kbAccountId, final List customFields, final Iterable properties, final CallContext callContext) { - return null; + return new DefaultNoOpHostedPaymentPageFormDescriptor(kbAccountId); } @Override public GatewayNotification processNotification(final String notification, final Iterable properties, final CallContext callContext) throws PaymentPluginApiException { - return null; + return new DefaultNoOpGatewayNotification(); } @Override diff --git a/pom.xml b/pom.xml index aeda3f90c3..e75a760e43 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.31 + 0.33-SNAPSHOT killbill 0.15.2-SNAPSHOT diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java new file mode 100644 index 0000000000..8da0baa099 --- /dev/null +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 Groupon, Inc + * Copyright 2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs; + +import java.util.UUID; + +import org.killbill.billing.client.model.Account; +import org.killbill.billing.client.model.ComboHostedPaymentPage; +import org.killbill.billing.client.model.HostedPaymentPageFields; +import org.killbill.billing.client.model.HostedPaymentPageFormDescriptor; +import org.killbill.billing.client.model.PaymentMethod; +import org.killbill.billing.client.model.PaymentMethodPluginDetail; +import org.killbill.billing.client.model.PluginProperty; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.ning.http.client.Response; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class TestPaymentGateway extends TestJaxrsBase { + + @Test(groups = "slow") + public void testBuildFormDescriptor() throws Exception { + final Account account = createAccountWithDefaultPaymentMethod(); + + final HostedPaymentPageFields hppFields = new HostedPaymentPageFields(); + + final HostedPaymentPageFormDescriptor hostedPaymentPageFormDescriptor = killBillClient.buildFormDescriptor(hppFields, account.getAccountId(), null, ImmutableMap.of(), createdBy, reason, comment); + Assert.assertEquals(hostedPaymentPageFormDescriptor.getKbAccountId(), account.getAccountId()); + } + + @Test(groups = "slow") + public void testComboBuildFormDescriptor() throws Exception { + final Account account = getAccount(); + account.setAccountId(null); + + final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail(); + final PaymentMethod paymentMethod = new PaymentMethod(null, UUID.randomUUID().toString(), null, true, PLUGIN_NAME, info); + + final HostedPaymentPageFields hppFields = new HostedPaymentPageFields(); + + final ComboHostedPaymentPage comboHostedPaymentPage = new ComboHostedPaymentPage(account, paymentMethod, ImmutableList.of(), hppFields); + + final HostedPaymentPageFormDescriptor hostedPaymentPageFormDescriptor = killBillClient.buildFormDescriptor(comboHostedPaymentPage, ImmutableMap.of(), createdBy, reason, comment); + Assert.assertNotNull(hostedPaymentPageFormDescriptor.getKbAccountId()); + } + + @Test(groups = "slow") + public void testProcessNotification() throws Exception { + final Response response = killBillClient.processNotification("TOTO", PLUGIN_NAME, ImmutableMap.of(), createdBy, reason, comment); + Assert.assertEquals(response.getStatusCode(), 200); + } +} From a06e75dc6c4e25914647fb0e9808ad1ebd461e47 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 05:26:24 -0700 Subject: [PATCH 002/137] payment: add missing pending states in PaymentStateMachineHelper#getPendingStateForTransaction This fixes https://github.com/killbill/killbill/issues/363. Signed-off-by: Pierre-Alexandre Meyer --- .../payment/core/sm/PaymentStateMachineHelper.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java index d6a12c4576..db1e2fada4 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java @@ -58,6 +58,9 @@ public class PaymentStateMachineHelper { private static final String CHARGEBACK_SUCCESS = "CHARGEBACK_SUCCESS"; private static final String AUTHORIZE_PENDING = "AUTH_PENDING"; + private static final String PURCHASE_PENDING = "PURCHASE_PENDING"; + private static final String REFUND_PENDING = "REFUND_PENDING"; + private static final String CREDIT_PENDING = "CREDIT_PENDING"; private static final String AUTHORIZE_FAILED = "AUTH_FAILED"; private static final String CAPTURE_FAILED = "CAPTURE_FAILED"; @@ -116,8 +119,14 @@ public String getPendingStateForTransaction(final TransactionType transactionTyp switch (transactionType) { case AUTHORIZE: return AUTHORIZE_PENDING; + case PURCHASE: + return PURCHASE_PENDING; + case REFUND: + return REFUND_PENDING; + case CREDIT: + return CREDIT_PENDING; default: - throw new IllegalStateException("Unsupported transaction type " + transactionType); + throw new IllegalStateException("No PENDING state for transaction type " + transactionType); } } From ee7a00ff46a7ade54557b1cfc5b89d51f364e887 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 09:00:18 -0700 Subject: [PATCH 003/137] jaxrs: add API to authorize/purchase/credit by account external key This fixes https://github.com/killbill/killbill/issues/368. Signed-off-by: Pierre-Alexandre Meyer --- .../jaxrs/resources/AccountResource.java | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java index bb57cb7b0d..29364d05a8 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java @@ -705,6 +705,30 @@ public PaymentJson apply(final Payment payment) { return Response.status(Response.Status.OK).entity(result).build(); } + @Timed + @POST + @Path("/" + PAYMENTS) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Trigger a payment using the account external key (authorization, purchase or credit)") + @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account external key supplied"), + @ApiResponse(code = 404, message = "Account not found")}) + public Response processPaymentByExternalKey(final PaymentTransactionJson json, + @QueryParam(QUERY_EXTERNAL_KEY) final String externalKey, + @QueryParam(QUERY_PAYMENT_METHOD_ID) final String paymentMethodIdStr, + @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List paymentControlPluginNames, + @QueryParam(QUERY_PLUGIN_PROPERTY) final List pluginPropertiesString, + @HeaderParam(HDR_CREATED_BY) final String createdBy, + @HeaderParam(HDR_REASON) final String reason, + @HeaderParam(HDR_COMMENT) final String comment, + @javax.ws.rs.core.Context final UriInfo uriInfo, + @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException { + final CallContext callContext = context.createContext(createdBy, reason, comment, request); + final Account account = accountUserApi.getAccountByKey(externalKey, callContext); + + return processPayment(json, account, paymentMethodIdStr, paymentControlPluginNames, pluginPropertiesString, uriInfo, callContext); + } + @Timed @POST @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS) @@ -723,19 +747,30 @@ public Response processPayment(final PaymentTransactionJson json, @HeaderParam(HDR_COMMENT) final String comment, @javax.ws.rs.core.Context final UriInfo uriInfo, @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException { + final UUID accountId = UUID.fromString(accountIdStr); + final CallContext callContext = context.createContext(createdBy, reason, comment, request); + final Account account = accountUserApi.getAccountById(accountId, callContext); + + return processPayment(json, account, paymentMethodIdStr, paymentControlPluginNames, pluginPropertiesString, uriInfo, callContext); + } + + private Response processPayment(final PaymentTransactionJson json, + final Account account, + final String paymentMethodIdStr, + final List paymentControlPluginNames, + final List pluginPropertiesString, + final UriInfo uriInfo, + final CallContext callContext) throws PaymentApiException { verifyNonNullOrEmpty(json, "PaymentTransactionJson body should be specified"); verifyNonNullOrEmpty(json.getTransactionType(), "PaymentTransactionJson transactionType needs to be set", json.getAmount(), "PaymentTransactionJson amount needs to be set"); final Iterable pluginProperties = extractPluginProperties(pluginPropertiesString); - final CallContext callContext = context.createContext(createdBy, reason, comment, request); - final UUID accountId = UUID.fromString(accountIdStr); - final Account account = accountUserApi.getAccountById(accountId, callContext); final UUID paymentMethodId = paymentMethodIdStr == null ? account.getPaymentMethodId() : UUID.fromString(paymentMethodIdStr); final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency()); final UUID paymentId = json.getPaymentId() == null ? null : UUID.fromString(json.getPaymentId()); - validatePaymentMethodForAccount(accountId, paymentMethodId, callContext); + validatePaymentMethodForAccount(account.getId(), paymentMethodId, callContext); final TransactionType transactionType = TransactionType.valueOf(json.getTransactionType()); final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames); From ac72e589ccd60cf4b49316662e678f242834fbf5 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 10:49:16 -0700 Subject: [PATCH 004/137] payment: don't require amount/currency for completion operations This fixes https://github.com/killbill/killbill/issues/372. Signed-off-by: Pierre-Alexandre Meyer --- .../payment/api/DefaultPaymentApi.java | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java index d329f56878..1e2d91d217 100644 --- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java @@ -72,10 +72,12 @@ public Payment createAuthorization(final Account account, final UUID paymentMeth final Iterable properties, final CallContext callContext) throws PaymentApiException { checkNotNullParameter(account, "account"); checkNotNullParameter(paymentMethodId, "paymentMethodId"); - checkNotNullParameter(amount, "amount"); - checkNotNullParameter(currency, "currency"); + if (paymentId == null) { + checkNotNullParameter(amount, "amount"); + checkPositiveAmount(amount); + checkNotNullParameter(currency, "currency"); + } checkNotNullParameter(properties, "plugin properties"); - checkPositiveAmount(amount); logAPICall(TransactionType.AUTHORIZE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey); @@ -95,10 +97,12 @@ public Payment createAuthorizationWithPaymentControl(final Account account, fina checkNotNullParameter(account, "account"); checkNotNullParameter(paymentMethodId, "paymentMethodId"); - checkNotNullParameter(amount, "amount"); - checkNotNullParameter(currency, "currency"); + if (paymentId == null) { + checkNotNullParameter(amount, "amount"); + checkPositiveAmount(amount); + checkNotNullParameter(currency, "currency"); + } checkNotNullParameter(properties, "plugin properties"); - checkPositiveAmount(amount); logAPICall(TransactionType.AUTHORIZE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey); @@ -148,10 +152,12 @@ public Payment createPurchase(final Account account, final UUID paymentMethodId, final Iterable properties, final CallContext callContext) throws PaymentApiException { checkNotNullParameter(account, "account"); checkNotNullParameter(paymentMethodId, "paymentMethodId"); - checkNotNullParameter(amount, "amount"); - checkNotNullParameter(currency, "currency"); + if (paymentId == null) { + checkNotNullParameter(amount, "amount"); + checkPositiveAmount(amount); + checkNotNullParameter(currency, "currency"); + } checkNotNullParameter(properties, "plugin properties"); - checkPositiveAmount(amount); logAPICall(TransactionType.PURCHASE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey); @@ -169,12 +175,14 @@ public Payment createPurchaseWithPaymentControl(final Account account, @Nullable } checkNotNullParameter(account, "account"); - checkNotNullParameter(amount, "amount"); - checkNotNullParameter(currency, "currency"); + if (paymentId == null) { + checkNotNullParameter(amount, "amount"); + checkPositiveAmount(amount); + checkNotNullParameter(currency, "currency"); + } checkNotNullParameter(paymentExternalKey, "paymentExternalKey"); checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey"); checkNotNullParameter(properties, "plugin properties"); - checkPositiveAmount(amount); logAPICall(TransactionType.PURCHASE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey); @@ -227,15 +235,17 @@ public Payment createVoidWithPaymentControl(final Account account, final UUID pa } @Override - public Payment createRefund(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey, final Iterable properties, + public Payment createRefund(final Account account, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey, final Iterable properties, final CallContext callContext) throws PaymentApiException { - checkNotNullParameter(account, "account"); - checkNotNullParameter(amount, "amount"); - checkNotNullParameter(currency, "currency"); + if (paymentId == null) { + checkNotNullParameter(currency, "currency"); + } checkNotNullParameter(paymentId, "paymentId"); checkNotNullParameter(properties, "plugin properties"); - checkPositiveAmount(amount); + if (amount != null) { + checkPositiveAmount(amount); + } logAPICall(TransactionType.REFUND.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey); @@ -245,7 +255,7 @@ public Payment createRefund(final Account account, final UUID paymentId, final B } @Override - public Payment createRefundWithPaymentControl(final Account account, final UUID paymentId, @Nullable final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final Iterable properties, + public Payment createRefundWithPaymentControl(final Account account, @Nullable final UUID paymentId, @Nullable final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final Iterable properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException { final List paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions); if (paymentControlPluginNames.isEmpty()) { @@ -253,7 +263,9 @@ public Payment createRefundWithPaymentControl(final Account account, final UUID } checkNotNullParameter(account, "account"); - checkNotNullParameter(currency, "currency"); + if (paymentId == null) { + checkNotNullParameter(currency, "currency"); + } checkNotNullParameter(paymentId, "paymentId"); checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey"); checkNotNullParameter(properties, "plugin properties"); @@ -275,10 +287,12 @@ public Payment createCredit(final Account account, final UUID paymentMethodId, @ final Iterable properties, final CallContext callContext) throws PaymentApiException { checkNotNullParameter(account, "account"); checkNotNullParameter(paymentMethodId, "paymentMethodId"); - checkNotNullParameter(amount, "amount"); - checkNotNullParameter(currency, "currency"); + if (paymentId == null) { + checkNotNullParameter(amount, "amount"); + checkPositiveAmount(amount); + checkNotNullParameter(currency, "currency"); + } checkNotNullParameter(properties, "plugin properties"); - checkPositiveAmount(amount); logAPICall(TransactionType.CREDIT.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey); @@ -299,10 +313,12 @@ public Payment createCreditWithPaymentControl(final Account account, final UUID checkNotNullParameter(account, "account"); checkNotNullParameter(paymentMethodId, "paymentMethodId"); - checkNotNullParameter(amount, "amount"); - checkNotNullParameter(currency, "currency"); + if (paymentId == null) { + checkNotNullParameter(amount, "amount"); + checkPositiveAmount(amount); + checkNotNullParameter(currency, "currency"); + } checkNotNullParameter(properties, "plugin properties"); - checkPositiveAmount(amount); logAPICall(TransactionType.CREDIT.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey); From e11aa6e5c206c03a2b8f55f991fc97d55f3b5222 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 10:50:15 -0700 Subject: [PATCH 005/137] jaxrs: add missing payment complete API by payment id / external key This fixes https://github.com/killbill/killbill/issues/369. Signed-off-by: Pierre-Alexandre Meyer --- .../jaxrs/resources/PaymentResource.java | 90 ++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java index 1122a21fb1..a96e3b0de4 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java @@ -1,6 +1,6 @@ /* - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -33,6 +33,7 @@ import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -222,6 +223,91 @@ public PaymentJson apply(final Payment payment) { ); } + @Timed + @PUT + @Path("/{paymentId:" + UUID_PATTERN + "}") + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Complete an existing transaction") + @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid paymentId supplied"), + @ApiResponse(code = 404, message = "Account or payment not found")}) + public Response completeTransaction(final PaymentTransactionJson json, + @PathParam("paymentId") final String paymentIdStr, + @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List paymentControlPluginNames, + @QueryParam(QUERY_PLUGIN_PROPERTY) final List pluginPropertiesString, + @HeaderParam(HDR_CREATED_BY) final String createdBy, + @HeaderParam(HDR_REASON) final String reason, + @HeaderParam(HDR_COMMENT) final String comment, + @javax.ws.rs.core.Context final UriInfo uriInfo, + @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException { + return completeTransactionInternal(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request); + } + + @Timed + @PUT + @Path("/") + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Complete an existing transaction") + @ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment not found")}) + public Response completeTransactionByExternalKey(final PaymentTransactionJson json, + @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List paymentControlPluginNames, + @QueryParam(QUERY_PLUGIN_PROPERTY) final List pluginPropertiesString, + @HeaderParam(HDR_CREATED_BY) final String createdBy, + @HeaderParam(HDR_REASON) final String reason, + @HeaderParam(HDR_COMMENT) final String comment, + @javax.ws.rs.core.Context final UriInfo uriInfo, + @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException { + return completeTransactionInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request); + } + + private Response completeTransactionInternal(final PaymentTransactionJson json, + @Nullable final String paymentIdStr, + final List paymentControlPluginNames, + final Iterable pluginPropertiesString, + final String createdBy, + final String reason, + final String comment, + final UriInfo uriInfo, + final HttpServletRequest request) throws PaymentApiException, AccountApiException { + verifyNonNullOrEmpty(json, "PaymentTransactionJson body should be specified"); + verifyNonNullOrEmpty(json.getTransactionType(), "PaymentTransactionJson transactionType needs to be set"); + if (paymentIdStr == null) { + verifyNonNullOrEmpty(json.getPaymentExternalKey(), "PaymentTransactionJson externalKey needs to be set"); + } + + final Iterable pluginProperties = extractPluginProperties(pluginPropertiesString); + final CallContext callContext = context.createContext(createdBy, reason, comment, request); + final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json.getPaymentExternalKey(), pluginProperties, callContext); + + final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext); + final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency()); + + final TransactionType transactionType = TransactionType.valueOf(json.getTransactionType()); + final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames); + switch (transactionType) { + case AUTHORIZE: + paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), json.getAmount(), currency, + json.getPaymentExternalKey(), json.getTransactionExternalKey(), + pluginProperties, paymentOptions, callContext); + break; + case PURCHASE: + paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), json.getAmount(), currency, + json.getPaymentExternalKey(), json.getTransactionExternalKey(), + pluginProperties, paymentOptions, callContext); + break; + case CREDIT: + paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), json.getAmount(), currency, + json.getPaymentExternalKey(), json.getTransactionExternalKey(), + pluginProperties, paymentOptions, callContext); + break; + default: + // It looks like we need at least REFUND? See https://github.com/killbill/killbill/issues/371 + return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " cannot be completed").build(); + } + return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", initialPayment.getId()); + } + @Timed @POST @Path("/{paymentId:" + UUID_PATTERN + "}/") From 9f60de4c3b4519b4ee1e0028d7c3f5310912fd03 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 12:50:56 -0700 Subject: [PATCH 006/137] payment: reduce logging in IncompletePaymentTransactionTask This fixes https://github.com/killbill/killbill/issues/367. Signed-off-by: Pierre-Alexandre Meyer --- .../IncompletePaymentTransactionTask.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java index cdc58a76d5..8c013d4cd7 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java @@ -188,8 +188,13 @@ private boolean updatePaymentAndTransactionInternal(final PaymentModelDao paymen case PLUGIN_FAILURE: case UNKNOWN: default: - log.info("Janitor IncompletePaymentTransactionTask unable to repair payment {}, transaction {}: {} -> {}", - payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); + if (transactionStatus == paymentTransaction.getTransactionStatus()) { + log.debug("Janitor IncompletePaymentTransactionTask unable to repair payment {}, transaction {}: {} -> {}", + payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); + } else { + log.info("Janitor IncompletePaymentTransactionTask unable to repair payment {}, transaction {}: {} -> {}", + payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); + } // We can't get anything interesting from the plugin... insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId()); return false; @@ -208,8 +213,13 @@ private boolean updatePaymentAndTransactionInternal(final PaymentModelDao paymen final String gatewayErrorCode = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayErrorCode() : paymentTransaction.getGatewayErrorCode(); final String gatewayError = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayError() : paymentTransaction.getGatewayErrorMsg(); - log.info("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}", - payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); + if (transactionStatus == paymentTransaction.getTransactionStatus()) { + log.debug("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}", + payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); + } else { + log.info("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}", + payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); + } final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext); paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(), payment.getId(), paymentTransaction.getTransactionType(), newPaymentState, lastSuccessPaymentState, From f6e13d42af033ac09305d8206fb800ee072b2239 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 12:54:38 -0700 Subject: [PATCH 007/137] payment: protect PaymentTransactionInfoPluginConverter against NPE This fixes https://github.com/killbill/killbill/issues/366. Signed-off-by: Pierre-Alexandre Meyer --- .../core/PaymentTransactionInfoPluginConverter.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentTransactionInfoPluginConverter.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentTransactionInfoPluginConverter.java index bcdc703205..62d7ccdc5b 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentTransactionInfoPluginConverter.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentTransactionInfoPluginConverter.java @@ -17,12 +17,13 @@ package org.killbill.billing.payment.core; -import javax.annotation.Nullable; - import org.killbill.automaton.OperationResult; import org.killbill.billing.payment.api.TransactionStatus; +import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import com.google.common.base.MoreObjects; + // // Conversion between the plugin result to the payment state and transaction status // @@ -30,7 +31,8 @@ public class PaymentTransactionInfoPluginConverter { public static TransactionStatus toTransactionStatus(final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin) { - switch (paymentTransactionInfoPlugin.getStatus()) { + final PaymentPluginStatus status = MoreObjects.firstNonNull(paymentTransactionInfoPlugin.getStatus(), PaymentPluginStatus.UNDEFINED); + switch (status) { case PROCESSED: return TransactionStatus.SUCCESS; case PENDING: @@ -54,7 +56,8 @@ public static TransactionStatus toTransactionStatus(final PaymentTransactionInfo } public static OperationResult toOperationResult(final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin) { - switch (paymentTransactionInfoPlugin.getStatus()) { + final PaymentPluginStatus status = MoreObjects.firstNonNull(paymentTransactionInfoPlugin.getStatus(), PaymentPluginStatus.UNDEFINED); + switch (status) { case PROCESSED: return OperationResult.SUCCESS; case PENDING: From 6f35287b6b0dd7162bbefe699dba32124a37f792 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 15:27:28 -0700 Subject: [PATCH 008/137] payment: add tests for completion operations This is part of https://github.com/killbill/killbill/issues/372. Signed-off-by: Pierre-Alexandre Meyer --- .../payment/core/sm/PaymentStateContext.java | 6 +- .../billing/payment/api/TestPaymentApi.java | 172 ++++++++++++++++-- .../provider/MockPaymentProviderPlugin.java | 43 ++++- 3 files changed, 193 insertions(+), 28 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java index d9c097c0c3..cbe72e7172 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java @@ -171,11 +171,13 @@ public void setAttemptId(final UUID attemptId) { } public BigDecimal getAmount() { - return amount; + // For a complete operation, if the amount isn't specified, take the original amount on the transaction + return amount == null && paymentTransactionModelDao != null ? paymentTransactionModelDao.getAmount() : amount; } public Currency getCurrency() { - return currency; + // For a complete operation, if the currency isn't specified, take the original currency on the transaction + return currency == null && paymentTransactionModelDao != null ? paymentTransactionModelDao.getCurrency() : currency; } public TransactionType getTransactionType() { diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java index e4d7b09f04..91d1d65aba 100644 --- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java +++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java @@ -24,10 +24,13 @@ import java.util.List; import java.util.UUID; +import javax.annotation.Nullable; + import org.joda.time.LocalDate; import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.control.plugin.api.PaymentControlApiException; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItem; @@ -36,7 +39,8 @@ import org.killbill.billing.payment.dao.PaymentAttemptModelDao; import org.killbill.billing.payment.dao.PaymentSqlDao; import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi; -import org.killbill.billing.control.plugin.api.PaymentControlApiException; +import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; +import org.killbill.billing.payment.provider.MockPaymentProviderPlugin; import org.killbill.bus.api.PersistentBus.EventBusException; import org.testng.Assert; import org.testng.annotations.BeforeMethod; @@ -608,30 +612,166 @@ public void testApiRetryWithUnknownPaymentTransaction() throws Exception { // Example of a 3D secure payment for instance @Test(groups = "slow") public void testApiWithPendingPaymentTransaction() throws Exception { - final BigDecimal requestedAmount = BigDecimal.TEN; + for (final TransactionType transactionType : ImmutableList.of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) { + testApiWithPendingPaymentTransaction(transactionType, BigDecimal.TEN, BigDecimal.TEN); + testApiWithPendingPaymentTransaction(transactionType, BigDecimal.TEN, BigDecimal.ONE); + testApiWithPendingPaymentTransaction(transactionType, BigDecimal.TEN, null); + } + } + @Test(groups = "slow") + public void testApiWithPendingRefundPaymentTransaction() throws Exception { final String paymentExternalKey = UUID.randomUUID().toString(); final String paymentTransactionExternalKey = UUID.randomUUID().toString(); + final String refundTransactionExternalKey = UUID.randomUUID().toString(); + final BigDecimal requestedAmount = BigDecimal.TEN; + final BigDecimal refundAmount = BigDecimal.ONE; + final Iterable pendingPluginProperties = ImmutableList.of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, TransactionStatus.PENDING.toString(), false)); - final Payment pendingPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), - paymentExternalKey, paymentTransactionExternalKey, ImmutableList.of(), callContext); + final Payment payment = createPayment(TransactionType.PURCHASE, null, paymentExternalKey, paymentTransactionExternalKey, requestedAmount, PaymentPluginStatus.PROCESSED); + Assert.assertNotNull(payment); + Assert.assertEquals(payment.getExternalKey(), paymentExternalKey); + Assert.assertEquals(payment.getTransactions().size(), 1); + Assert.assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(payment.getTransactions().get(0).getCurrency(), account.getCurrency()); + Assert.assertEquals(payment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); + Assert.assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS); - final String paymentStateName = paymentSMHelper.getPendingStateForTransaction(TransactionType.AUTHORIZE).toString(); - paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), pendingPayment.getId(), TransactionType.AUTHORIZE, paymentStateName, paymentStateName, - pendingPayment.getTransactions().get(0).getId(), TransactionStatus.PENDING, requestedAmount, account.getCurrency(), - null, null, internalCallContext); + final Payment pendingRefund = paymentApi.createRefund(account, + payment.getId(), + requestedAmount, + account.getCurrency(), + refundTransactionExternalKey, + pendingPluginProperties, + callContext); + verifyRefund(pendingRefund, paymentExternalKey, paymentTransactionExternalKey, refundTransactionExternalKey, requestedAmount, requestedAmount, TransactionStatus.PENDING); + + final Payment pendingRefund2 = paymentApi.createRefund(account, + payment.getId(), + null, + null, + refundTransactionExternalKey, + pendingPluginProperties, + callContext); + verifyRefund(pendingRefund2, paymentExternalKey, paymentTransactionExternalKey, refundTransactionExternalKey, requestedAmount, requestedAmount, TransactionStatus.PENDING); + + // Note: we change the refund amount + final Payment pendingRefund3 = paymentApi.createRefund(account, + payment.getId(), + refundAmount, + account.getCurrency(), + refundTransactionExternalKey, + pendingPluginProperties, + callContext); + verifyRefund(pendingRefund3, paymentExternalKey, paymentTransactionExternalKey, refundTransactionExternalKey, requestedAmount, refundAmount, TransactionStatus.PENDING); + + // Pass null, we revert back to the original refund amount + final Payment pendingRefund4 = paymentApi.createRefund(account, + payment.getId(), + null, + null, + refundTransactionExternalKey, + ImmutableList.of(), + callContext); + verifyRefund(pendingRefund4, paymentExternalKey, paymentTransactionExternalKey, refundTransactionExternalKey, requestedAmount, requestedAmount, TransactionStatus.SUCCESS); - final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), pendingPayment.getId(), requestedAmount, account.getCurrency(), - paymentExternalKey, paymentTransactionExternalKey, ImmutableList.of(), callContext); + } - Assert.assertEquals(payment.getId(), pendingPayment.getId()); - Assert.assertEquals(payment.getExternalKey(), paymentExternalKey); - Assert.assertEquals(payment.getExternalKey(), paymentExternalKey); + private void verifyRefund(final Payment refund, final String paymentExternalKey, final String paymentTransactionExternalKey, final String refundTransactionExternalKey, final BigDecimal requestedAmount, final BigDecimal refundAmount, final TransactionStatus transactionStatus) { + Assert.assertEquals(refund.getExternalKey(), paymentExternalKey); + Assert.assertEquals(refund.getTransactions().size(), 2); + Assert.assertEquals(refund.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(refund.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(refund.getTransactions().get(0).getCurrency(), account.getCurrency()); + Assert.assertEquals(refund.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); + Assert.assertEquals(refund.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS); + Assert.assertEquals(refund.getTransactions().get(1).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(refund.getTransactions().get(1).getProcessedAmount().compareTo(refundAmount), 0); + Assert.assertEquals(refund.getTransactions().get(1).getCurrency(), account.getCurrency()); + Assert.assertEquals(refund.getTransactions().get(1).getExternalKey(), refundTransactionExternalKey); + Assert.assertEquals(refund.getTransactions().get(1).getTransactionStatus(), transactionStatus); + } - Assert.assertEquals(payment.getTransactions().size(), 1); - Assert.assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS); - Assert.assertEquals(payment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); + private Payment testApiWithPendingPaymentTransaction(final TransactionType transactionType, final BigDecimal requestedAmount, @Nullable final BigDecimal pendingAmount) throws PaymentApiException { + final String paymentExternalKey = UUID.randomUUID().toString(); + final String paymentTransactionExternalKey = UUID.randomUUID().toString(); + final Payment pendingPayment = createPayment(transactionType, null, paymentExternalKey, paymentTransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING); + Assert.assertNotNull(pendingPayment); + Assert.assertEquals(pendingPayment.getExternalKey(), paymentExternalKey); + Assert.assertEquals(pendingPayment.getTransactions().size(), 1); + Assert.assertEquals(pendingPayment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment.getTransactions().get(0).getCurrency(), account.getCurrency()); + Assert.assertEquals(pendingPayment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); + Assert.assertEquals(pendingPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING); + + final Payment pendingPayment2 = createPayment(transactionType, pendingPayment.getId(), paymentExternalKey, paymentTransactionExternalKey, pendingAmount, PaymentPluginStatus.PENDING); + Assert.assertNotNull(pendingPayment2); + Assert.assertEquals(pendingPayment2.getExternalKey(), paymentExternalKey); + Assert.assertEquals(pendingPayment2.getTransactions().size(), 1); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getProcessedAmount().compareTo(pendingAmount == null ? requestedAmount : pendingAmount), 0); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getCurrency(), account.getCurrency()); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING); + + final Payment completedPayment = createPayment(transactionType, pendingPayment.getId(), paymentExternalKey, paymentTransactionExternalKey, pendingAmount, PaymentPluginStatus.PROCESSED); + Assert.assertNotNull(completedPayment); + Assert.assertEquals(completedPayment.getExternalKey(), paymentExternalKey); + Assert.assertEquals(completedPayment.getTransactions().size(), 1); + Assert.assertEquals(completedPayment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(completedPayment.getTransactions().get(0).getProcessedAmount().compareTo(pendingAmount == null ? requestedAmount : pendingAmount), 0); + Assert.assertEquals(completedPayment.getTransactions().get(0).getCurrency(), account.getCurrency()); + Assert.assertEquals(completedPayment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); + Assert.assertEquals(completedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS); + + return completedPayment; + } + + private Payment createPayment(final TransactionType transactionType, + @Nullable final UUID paymentId, + @Nullable final String paymentExternalKey, + @Nullable final String paymentTransactionExternalKey, + @Nullable final BigDecimal amount, + final PaymentPluginStatus paymentPluginStatus) throws PaymentApiException { + final Iterable pluginProperties = ImmutableList.of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, paymentPluginStatus.toString(), false)); + switch (transactionType) { + case AUTHORIZE: + return paymentApi.createAuthorization(account, + account.getPaymentMethodId(), + paymentId, + amount, + amount == null ? null : account.getCurrency(), + paymentExternalKey, + paymentTransactionExternalKey, + pluginProperties, + callContext); + case PURCHASE: + return paymentApi.createPurchase(account, + account.getPaymentMethodId(), + paymentId, + amount, + amount == null ? null : account.getCurrency(), + paymentExternalKey, + paymentTransactionExternalKey, + pluginProperties, + callContext); + case CREDIT: + return paymentApi.createCredit(account, + account.getPaymentMethodId(), + paymentId, + amount, + amount == null ? null : account.getCurrency(), + paymentExternalKey, + paymentTransactionExternalKey, + pluginProperties, + callContext); + default: + Assert.fail(); + return null; + } } private List createPropertiesForInvoice(final Invoice invoice) { diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java index 0d3e7ce0be..bd323a4f69 100644 --- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java +++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java @@ -20,6 +20,7 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; @@ -55,6 +56,8 @@ */ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi { + public static final String PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE = "paymentPluginStatusOverride"; + public static final String PLUGIN_NAME = "__NO_OP__"; private final AtomicBoolean makeNextInvoiceFailWithError = new AtomicBoolean(false); @@ -208,30 +211,30 @@ public void makeAllInvoicesFailWithError(final boolean failure) { @Override public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency); + return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency, properties); } @Override public PaymentTransactionInfoPlugin capturePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency); + return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency, properties); } @Override public PaymentTransactionInfoPlugin purchasePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency); + return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency, properties); } @Override public PaymentTransactionInfoPlugin voidPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null); + return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null, properties); } @Override public PaymentTransactionInfoPlugin creditPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency); + return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency, properties); } @Override @@ -325,23 +328,33 @@ public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final throw new PaymentPluginApiException("", String.format("Refund amount of %s for payment id %s is bigger than the payment amount %s (plugin %s)", refundAmount, kbPaymentId.toString(), maxAmountRefundable, PLUGIN_NAME)); } - return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency); + return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency, properties); } - private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, final BigDecimal amount, final Currency currency) throws PaymentPluginApiException { - + private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, final BigDecimal amount, final Currency currency, final Iterable pluginProperties) throws PaymentPluginApiException { if (makeNextInvoiceFailWithException.getAndSet(false)) { throw new PaymentPluginApiException("", "test error"); } - final PaymentPluginStatus status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED; + final PluginProperty paymentPluginStatusOverride = Iterables.tryFind(pluginProperties, new Predicate() { + @Override + public boolean apply(final PluginProperty input) { + return PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE.equals(input.getKey()); + } + }).orNull(); + + final PaymentPluginStatus status; + if (paymentPluginStatusOverride != null && paymentPluginStatusOverride.getValue() != null) { + status = PaymentPluginStatus.valueOf(paymentPluginStatusOverride.getValue().toString()); + } else { + status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED; + } InternalPaymentInfo info = payments.get(kbPaymentId.toString()); if (info == null) { info = new InternalPaymentInfo(); payments.put(kbPaymentId.toString(), info); } - info.addAmount(type, amount); final PaymentTransactionInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, type, amount, currency, clock.getUTCNow(), clock.getUTCNow(), status, null); List existingTransactions = paymentTransactions.get(kbPaymentId.toString()); @@ -350,7 +363,17 @@ private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final paymentTransactions.put(kbPaymentId.toString(), existingTransactions); } + final Iterator iterator = existingTransactions.iterator(); + while (iterator.hasNext()) { + final PaymentTransactionInfoPlugin existingTransaction = iterator.next(); + if (existingTransaction.getKbTransactionPaymentId().equals(kbTransactionId)) { + info.addAmount(type, existingTransaction.getAmount().negate()); + iterator.remove(); + } + } existingTransactions.add(result); + info.addAmount(type, result.getAmount()); + return result; } } From c6d52ad4a511840b47f97f0f10b6f5b2efa675f6 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 11 Aug 2015 16:15:43 -0700 Subject: [PATCH 009/137] payment: enhance complete operations tests Add regression tests for https://github.com/killbill/killbill/issues/363. This uncovered a couple of typos as well! Signed-off-by: Pierre-Alexandre Meyer --- .../payment/core/PaymentProcessor.java | 2 +- .../billing/payment/api/TestPaymentApi.java | 44 +++++++++++++++++++ .../provider/MockPaymentProviderPlugin.java | 2 +- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java index 53e1606408..3326bd30d6 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java @@ -386,7 +386,7 @@ private Payment toPayment(final PaymentModelDao paymentModelDao, @Nullable final final InternalTenantContext tenantContextWithAccountRecordId = getInternalTenantContextWithAccountRecordId(paymentModelDao.getAccountId(), tenantContext); final List transactionsForPayment = paymentDao.getTransactionsForPayment(paymentModelDao.getId(), tenantContextWithAccountRecordId); - return toPayment(paymentModelDao, transactionsForPayment, pluginTransactions, tenantContext); + return toPayment(paymentModelDao, transactionsForPayment, pluginTransactions, tenantContextWithAccountRecordId); } // Used in bulk get API (getAccountPayments) diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java index 91d1d65aba..19c04f6e30 100644 --- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java +++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java @@ -615,6 +615,7 @@ public void testApiWithPendingPaymentTransaction() throws Exception { for (final TransactionType transactionType : ImmutableList.of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) { testApiWithPendingPaymentTransaction(transactionType, BigDecimal.TEN, BigDecimal.TEN); testApiWithPendingPaymentTransaction(transactionType, BigDecimal.TEN, BigDecimal.ONE); + // See https://github.com/killbill/killbill/issues/372 testApiWithPendingPaymentTransaction(transactionType, BigDecimal.TEN, null); } } @@ -647,6 +648,10 @@ public void testApiWithPendingRefundPaymentTransaction() throws Exception { callContext); verifyRefund(pendingRefund, paymentExternalKey, paymentTransactionExternalKey, refundTransactionExternalKey, requestedAmount, requestedAmount, TransactionStatus.PENDING); + // Test Janitor path (regression test for https://github.com/killbill/killbill/issues/363) + verifyPaymentViaGetPath(pendingRefund); + + // See https://github.com/killbill/killbill/issues/372 final Payment pendingRefund2 = paymentApi.createRefund(account, payment.getId(), null, @@ -656,6 +661,8 @@ public void testApiWithPendingRefundPaymentTransaction() throws Exception { callContext); verifyRefund(pendingRefund2, paymentExternalKey, paymentTransactionExternalKey, refundTransactionExternalKey, requestedAmount, requestedAmount, TransactionStatus.PENDING); + verifyPaymentViaGetPath(pendingRefund2); + // Note: we change the refund amount final Payment pendingRefund3 = paymentApi.createRefund(account, payment.getId(), @@ -666,6 +673,8 @@ public void testApiWithPendingRefundPaymentTransaction() throws Exception { callContext); verifyRefund(pendingRefund3, paymentExternalKey, paymentTransactionExternalKey, refundTransactionExternalKey, requestedAmount, refundAmount, TransactionStatus.PENDING); + verifyPaymentViaGetPath(pendingRefund3); + // Pass null, we revert back to the original refund amount final Payment pendingRefund4 = paymentApi.createRefund(account, payment.getId(), @@ -676,6 +685,7 @@ public void testApiWithPendingRefundPaymentTransaction() throws Exception { callContext); verifyRefund(pendingRefund4, paymentExternalKey, paymentTransactionExternalKey, refundTransactionExternalKey, requestedAmount, requestedAmount, TransactionStatus.SUCCESS); + verifyPaymentViaGetPath(pendingRefund4); } private void verifyRefund(final Payment refund, final String paymentExternalKey, final String paymentTransactionExternalKey, final String refundTransactionExternalKey, final BigDecimal requestedAmount, final BigDecimal refundAmount, final TransactionStatus transactionStatus) { @@ -707,6 +717,9 @@ private Payment testApiWithPendingPaymentTransaction(final TransactionType trans Assert.assertEquals(pendingPayment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); Assert.assertEquals(pendingPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING); + // Test Janitor path (regression test for https://github.com/killbill/killbill/issues/363) + verifyPaymentViaGetPath(pendingPayment); + final Payment pendingPayment2 = createPayment(transactionType, pendingPayment.getId(), paymentExternalKey, paymentTransactionExternalKey, pendingAmount, PaymentPluginStatus.PENDING); Assert.assertNotNull(pendingPayment2); Assert.assertEquals(pendingPayment2.getExternalKey(), paymentExternalKey); @@ -717,6 +730,8 @@ private Payment testApiWithPendingPaymentTransaction(final TransactionType trans Assert.assertEquals(pendingPayment2.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); Assert.assertEquals(pendingPayment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING); + verifyPaymentViaGetPath(pendingPayment2); + final Payment completedPayment = createPayment(transactionType, pendingPayment.getId(), paymentExternalKey, paymentTransactionExternalKey, pendingAmount, PaymentPluginStatus.PROCESSED); Assert.assertNotNull(completedPayment); Assert.assertEquals(completedPayment.getExternalKey(), paymentExternalKey); @@ -727,9 +742,38 @@ private Payment testApiWithPendingPaymentTransaction(final TransactionType trans Assert.assertEquals(completedPayment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); Assert.assertEquals(completedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS); + verifyPaymentViaGetPath(completedPayment); + return completedPayment; } + private void verifyPaymentViaGetPath(final Payment payment) throws PaymentApiException { + // We can't use Assert.assertEquals because the updateDate may have been updated by the Janitor + final Payment refreshedPayment = paymentApi.getPayment(payment.getId(), true, ImmutableList.of(), callContext); + + Assert.assertEquals(refreshedPayment.getAccountId(), payment.getAccountId()); + + Assert.assertEquals(refreshedPayment.getTransactions().size(), payment.getTransactions().size()); + Assert.assertEquals(refreshedPayment.getExternalKey(), payment.getExternalKey()); + Assert.assertEquals(refreshedPayment.getPaymentMethodId(), payment.getPaymentMethodId()); + Assert.assertEquals(refreshedPayment.getAccountId(), payment.getAccountId()); + Assert.assertEquals(refreshedPayment.getAuthAmount().compareTo(payment.getAuthAmount()), 0); + Assert.assertEquals(refreshedPayment.getCapturedAmount().compareTo(payment.getCapturedAmount()), 0); + Assert.assertEquals(refreshedPayment.getPurchasedAmount().compareTo(payment.getPurchasedAmount()), 0); + Assert.assertEquals(refreshedPayment.getRefundedAmount().compareTo(payment.getRefundedAmount()), 0); + Assert.assertEquals(refreshedPayment.getCurrency(), payment.getCurrency()); + + for (int i = 0; i < refreshedPayment.getTransactions().size(); i++) { + final PaymentTransaction refreshedPaymentTransaction = refreshedPayment.getTransactions().get(i); + final PaymentTransaction paymentTransaction = payment.getTransactions().get(i); + Assert.assertEquals(refreshedPaymentTransaction.getAmount().compareTo(paymentTransaction.getAmount()), 0); + Assert.assertEquals(refreshedPaymentTransaction.getProcessedAmount().compareTo(paymentTransaction.getProcessedAmount()), 0); + Assert.assertEquals(refreshedPaymentTransaction.getCurrency(), paymentTransaction.getCurrency()); + Assert.assertEquals(refreshedPaymentTransaction.getExternalKey(), paymentTransaction.getExternalKey()); + Assert.assertEquals(refreshedPaymentTransaction.getTransactionStatus(), paymentTransaction.getTransactionStatus()); + } + } + private Payment createPayment(final TransactionType transactionType, @Nullable final UUID paymentId, @Nullable final String paymentExternalKey, diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java index bd323a4f69..9e8243d662 100644 --- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java +++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java @@ -317,7 +317,7 @@ public GatewayNotification processNotification(final String notification, final } @Override - public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbPaymentMethodId, final UUID kbTransactionId, final BigDecimal refundAmount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { + public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal refundAmount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { final InternalPaymentInfo info = payments.get(kbPaymentId.toString()); if (info == null) { From 408db8ce4b58595e46582a97ad1a48935c7bd6e4 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 12 Aug 2015 07:03:07 -0700 Subject: [PATCH 010/137] server: trivial refactoring of TestPayment Signed-off-by: Pierre-Alexandre Meyer --- .../killbill/billing/jaxrs/TestPayment.java | 185 ++++++++++-------- 1 file changed, 104 insertions(+), 81 deletions(-) diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java index 3aab61aee0..df635ae56b 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java @@ -1,6 +1,6 @@ /* - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -18,6 +18,7 @@ package org.killbill.billing.jaxrs; import java.math.BigDecimal; +import java.util.Map; import java.util.UUID; import javax.annotation.Nullable; @@ -31,11 +32,11 @@ import org.killbill.billing.client.model.PaymentTransaction; import org.killbill.billing.client.model.Payments; import org.killbill.billing.client.model.PluginProperty; -import org.killbill.billing.jaxrs.json.PluginPropertyJson; +import org.killbill.billing.payment.api.TransactionType; import org.testng.Assert; import org.testng.annotations.Test; -import com.google.common.base.Objects; +import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -74,33 +75,20 @@ public void testComboAuthorization() throws Exception { final ComboPaymentTransaction comboPaymentTransaction = new ComboPaymentTransaction(accountJson, paymentMethodJson, authTransactionJson, ImmutableList.of(), ImmutableList.of()); final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.of(), createdBy, reason, comment); - verifyComboPayment(payment, paymentExternalKey, - BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1); - + verifyComboPayment(payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1); // Void payment using externalKey final String voidTransactionExternalKey = UUID.randomUUID().toString(); final Payment voidPayment = killBillClient.voidPayment(null, paymentExternalKey, voidTransactionExternalKey, ImmutableMap.of(), createdBy, reason, comment); - verifyPaymentTransaction(voidPayment.getPaymentId(), voidPayment.getTransactions().get(1), - paymentExternalKey, voidTransactionExternalKey, - accountJson, null, "VOID"); - - + verifyPaymentTransaction(accountJson, voidPayment.getPaymentId(), paymentExternalKey, voidPayment.getTransactions().get(1), + voidTransactionExternalKey, null, "VOID"); } private void testCreateRetrievePayment(final Account account, @Nullable final UUID paymentMethodId, - final String PaymentExternalKey, final int PaymentNb) throws Exception { + final String paymentExternalKey, final int paymentNb) throws Exception { // Authorization final String authTransactionExternalKey = UUID.randomUUID().toString(); - final PaymentTransaction authTransaction = new PaymentTransaction(); - authTransaction.setAmount(BigDecimal.TEN); - authTransaction.setCurrency(account.getCurrency()); - authTransaction.setPaymentExternalKey(PaymentExternalKey); - authTransaction.setTransactionExternalKey(authTransactionExternalKey); - authTransaction.setTransactionType("AUTHORIZE"); - final Payment authPayment = killBillClient.createPayment(account.getAccountId(), paymentMethodId, authTransaction, createdBy, reason, comment); - verifyPayment(account, paymentMethodId, authPayment, PaymentExternalKey, authTransactionExternalKey, - BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, PaymentNb); + final Payment authPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, TransactionType.AUTHORIZE, ImmutableMap.of(), paymentNb); // Capture 1 final String capture1TransactionExternalKey = UUID.randomUUID().toString(); @@ -108,15 +96,14 @@ private void testCreateRetrievePayment(final Account account, @Nullable final UU captureTransaction.setPaymentId(authPayment.getPaymentId()); captureTransaction.setAmount(BigDecimal.ONE); captureTransaction.setCurrency(account.getCurrency()); - captureTransaction.setPaymentExternalKey(PaymentExternalKey); + captureTransaction.setPaymentExternalKey(paymentExternalKey); captureTransaction.setTransactionExternalKey(capture1TransactionExternalKey); // captureAuthorization is using paymentId final Payment capturedPayment1 = killBillClient.captureAuthorization(captureTransaction, createdBy, reason, comment); - verifyPayment(account, paymentMethodId, capturedPayment1, PaymentExternalKey, authTransactionExternalKey, - BigDecimal.TEN, BigDecimal.ONE, BigDecimal.ZERO, 2, PaymentNb); - verifyPaymentTransaction(authPayment.getPaymentId(), capturedPayment1.getTransactions().get(1), - PaymentExternalKey, capture1TransactionExternalKey, - account, captureTransaction.getAmount(), "CAPTURE"); + verifyPayment(account, paymentMethodId, capturedPayment1, paymentExternalKey, authTransactionExternalKey, + BigDecimal.TEN, BigDecimal.ONE, BigDecimal.ZERO, 2, paymentNb); + verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, capturedPayment1.getTransactions().get(1), + capture1TransactionExternalKey, captureTransaction.getAmount(), "CAPTURE"); // Capture 2 final String capture2TransactionExternalKey = UUID.randomUUID().toString(); @@ -124,11 +111,10 @@ private void testCreateRetrievePayment(final Account account, @Nullable final UU // captureAuthorization is using externalKey captureTransaction.setPaymentId(null); final Payment capturedPayment2 = killBillClient.captureAuthorization(captureTransaction, createdBy, reason, comment); - verifyPayment(account, paymentMethodId, capturedPayment2, PaymentExternalKey, authTransactionExternalKey, - BigDecimal.TEN, new BigDecimal("2"), BigDecimal.ZERO, 3, PaymentNb); - verifyPaymentTransaction(authPayment.getPaymentId(), capturedPayment2.getTransactions().get(2), - PaymentExternalKey, capture2TransactionExternalKey, - account, captureTransaction.getAmount(), "CAPTURE"); + verifyPayment(account, paymentMethodId, capturedPayment2, paymentExternalKey, authTransactionExternalKey, + BigDecimal.TEN, new BigDecimal("2"), BigDecimal.ZERO, 3, paymentNb); + verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, capturedPayment2.getTransactions().get(2), + capture2TransactionExternalKey, captureTransaction.getAmount(), "CAPTURE"); // Refund final String refundTransactionExternalKey = UUID.randomUUID().toString(); @@ -136,44 +122,33 @@ private void testCreateRetrievePayment(final Account account, @Nullable final UU refundTransaction.setPaymentId(authPayment.getPaymentId()); refundTransaction.setAmount(new BigDecimal("2")); refundTransaction.setCurrency(account.getCurrency()); - refundTransaction.setPaymentExternalKey(PaymentExternalKey); + refundTransaction.setPaymentExternalKey(paymentExternalKey); refundTransaction.setTransactionExternalKey(refundTransactionExternalKey); final Payment refundPayment = killBillClient.refundPayment(refundTransaction, createdBy, reason, comment); - verifyPayment(account, paymentMethodId, refundPayment, PaymentExternalKey, authTransactionExternalKey, - BigDecimal.TEN, new BigDecimal("2"), new BigDecimal("2"), 4, PaymentNb); - verifyPaymentTransaction(authPayment.getPaymentId(), refundPayment.getTransactions().get(3), - PaymentExternalKey, refundTransactionExternalKey, - account, refundTransaction.getAmount(), "REFUND"); + verifyPayment(account, paymentMethodId, refundPayment, paymentExternalKey, authTransactionExternalKey, + BigDecimal.TEN, new BigDecimal("2"), new BigDecimal("2"), 4, paymentNb); + verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, refundPayment.getTransactions().get(3), + refundTransactionExternalKey, refundTransaction.getAmount(), "REFUND"); } - private void verifyPayment(final Account account, @Nullable final UUID paymentMethodId, final Payment Payment, - final String PaymentExternalKey, final String authTransactionExternalKey, - final BigDecimal authAmount, final BigDecimal capturedAmount, - final BigDecimal refundedAmount, final int nbTransactions, final int PaymentNb) throws KillBillClientException { - Assert.assertEquals(Payment.getAccountId(), account.getAccountId()); - Assert.assertEquals(Payment.getPaymentMethodId(), Objects.firstNonNull(paymentMethodId, account.getPaymentMethodId())); - Assert.assertNotNull(Payment.getPaymentId()); - Assert.assertNotNull(Payment.getPaymentNumber()); - Assert.assertEquals(Payment.getPaymentExternalKey(), PaymentExternalKey); - Assert.assertEquals(Payment.getAuthAmount().compareTo(authAmount), 0); - Assert.assertEquals(Payment.getCapturedAmount().compareTo(capturedAmount), 0); - Assert.assertEquals(Payment.getRefundedAmount().compareTo(refundedAmount), 0); - Assert.assertEquals(Payment.getCurrency(), account.getCurrency()); - Assert.assertEquals(Payment.getTransactions().size(), nbTransactions); - - verifyPaymentTransaction(Payment.getPaymentId(), Payment.getTransactions().get(0), - PaymentExternalKey, authTransactionExternalKey, account, authAmount, "AUTHORIZE"); - - final Payments Payments = killBillClient.getPayments(); - Assert.assertEquals(Payments.size(), PaymentNb); - Assert.assertEquals(Payments.get(PaymentNb - 1), Payment); + private Payment createVerifyTransaction(final Account account, + @Nullable final UUID paymentMethodId, + final String paymentExternalKey, + final String transactionExternalKey, + final TransactionType transactionType, + final Map pluginProperties, + final int paymentNb) throws KillBillClientException { + final PaymentTransaction authTransaction = new PaymentTransaction(); + authTransaction.setAmount(BigDecimal.TEN); + authTransaction.setCurrency(account.getCurrency()); + authTransaction.setPaymentExternalKey(paymentExternalKey); + authTransaction.setTransactionExternalKey(transactionExternalKey); + authTransaction.setTransactionType(transactionType.toString()); + final Payment payment = killBillClient.createPayment(account.getAccountId(), paymentMethodId, authTransaction, pluginProperties, createdBy, reason, comment); - final Payment retrievedPayment = killBillClient.getPayment(Payment.getPaymentId()); - Assert.assertEquals(retrievedPayment, Payment); + verifyPaymentNoTransaction(account, paymentMethodId, payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); - final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(account.getAccountId()); - Assert.assertEquals(paymentsForAccount.size(), PaymentNb); - Assert.assertEquals(paymentsForAccount.get(PaymentNb - 1), Payment); + return payment; } private void verifyComboPayment(final Payment payment, @@ -182,36 +157,84 @@ private void verifyComboPayment(final Payment payment, final BigDecimal capturedAmount, final BigDecimal refundedAmount, final int nbTransactions, - final int PaymentNb) throws KillBillClientException { + final int paymentNb) throws KillBillClientException { + Assert.assertNotNull(payment.getPaymentNumber()); + Assert.assertEquals(payment.getPaymentExternalKey(), paymentExternalKey); + Assert.assertEquals(payment.getAuthAmount().compareTo(authAmount), 0); + Assert.assertEquals(payment.getCapturedAmount().compareTo(capturedAmount), 0); + Assert.assertEquals(payment.getRefundedAmount().compareTo(refundedAmount), 0); + Assert.assertEquals(payment.getTransactions().size(), nbTransactions); + + final Payments Payments = killBillClient.getPayments(); + Assert.assertEquals(Payments.size(), paymentNb); + Assert.assertEquals(Payments.get(paymentNb - 1), payment); + } + + private void verifyPayment(final Account account, + @Nullable final UUID paymentMethodId, + final Payment payment, + final String paymentExternalKey, + final String authTransactionExternalKey, + final BigDecimal authAmount, + final BigDecimal capturedAmount, + final BigDecimal refundedAmount, + final int nbTransactions, + final int paymentNb) throws KillBillClientException { + verifyPaymentNoTransaction(account, paymentMethodId, payment, paymentExternalKey, authAmount, capturedAmount, refundedAmount, nbTransactions, paymentNb); + verifyPaymentTransaction(account, payment.getPaymentId(), paymentExternalKey, payment.getTransactions().get(0), authTransactionExternalKey, authAmount, "AUTHORIZE"); + } + private void verifyPaymentNoTransaction(final Account account, + @Nullable final UUID paymentMethodId, + final Payment payment, + final String paymentExternalKey, + final BigDecimal authAmount, + final BigDecimal capturedAmount, + final BigDecimal refundedAmount, + final int nbTransactions, + final int paymentNb) throws KillBillClientException { + Assert.assertEquals(payment.getAccountId(), account.getAccountId()); + Assert.assertEquals(payment.getPaymentMethodId(), MoreObjects.firstNonNull(paymentMethodId, account.getPaymentMethodId())); + Assert.assertNotNull(payment.getPaymentId()); Assert.assertNotNull(payment.getPaymentNumber()); Assert.assertEquals(payment.getPaymentExternalKey(), paymentExternalKey); Assert.assertEquals(payment.getAuthAmount().compareTo(authAmount), 0); Assert.assertEquals(payment.getCapturedAmount().compareTo(capturedAmount), 0); Assert.assertEquals(payment.getRefundedAmount().compareTo(refundedAmount), 0); + Assert.assertEquals(payment.getCurrency(), account.getCurrency()); Assert.assertEquals(payment.getTransactions().size(), nbTransactions); final Payments Payments = killBillClient.getPayments(); - Assert.assertEquals(Payments.size(), PaymentNb); - Assert.assertEquals(Payments.get(PaymentNb - 1), payment); + Assert.assertEquals(Payments.size(), paymentNb); + Assert.assertEquals(Payments.get(paymentNb - 1), payment); + + final Payment retrievedPayment = killBillClient.getPayment(payment.getPaymentId()); + Assert.assertEquals(retrievedPayment, payment); + final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(account.getAccountId()); + Assert.assertEquals(paymentsForAccount.size(), paymentNb); + Assert.assertEquals(paymentsForAccount.get(paymentNb - 1), payment); } - private void verifyPaymentTransaction(final UUID PaymentId, final PaymentTransaction PaymentTransaction, - final String PaymentExternalKey, final String TransactionExternalKey, - final Account account, @Nullable final BigDecimal amount, final String transactionType) { - Assert.assertEquals(PaymentTransaction.getPaymentId(), PaymentId); - Assert.assertNotNull(PaymentTransaction.getTransactionId()); - Assert.assertEquals(PaymentTransaction.getTransactionType(), transactionType); - Assert.assertEquals(PaymentTransaction.getStatus(), "SUCCESS"); + private void verifyPaymentTransaction(final Account account, + final UUID paymentId, + final String paymentExternalKey, + final PaymentTransaction paymentTransaction, + final String transactionExternalKey, + @Nullable final BigDecimal amount, + final String transactionType) { + Assert.assertEquals(paymentTransaction.getPaymentId(), paymentId); + Assert.assertNotNull(paymentTransaction.getTransactionId()); + Assert.assertEquals(paymentTransaction.getTransactionType(), transactionType); + Assert.assertEquals(paymentTransaction.getStatus(), "SUCCESS"); if (amount == null) { - Assert.assertNull(PaymentTransaction.getAmount()); - Assert.assertNull(PaymentTransaction.getCurrency()); + Assert.assertNull(paymentTransaction.getAmount()); + Assert.assertNull(paymentTransaction.getCurrency()); } else { - Assert.assertEquals(PaymentTransaction.getAmount().compareTo(amount), 0); - Assert.assertEquals(PaymentTransaction.getCurrency(), account.getCurrency()); + Assert.assertEquals(paymentTransaction.getAmount().compareTo(amount), 0); + Assert.assertEquals(paymentTransaction.getCurrency(), account.getCurrency()); } - Assert.assertEquals(PaymentTransaction.getTransactionExternalKey(), TransactionExternalKey); - Assert.assertEquals(PaymentTransaction.getPaymentExternalKey(), PaymentExternalKey); + Assert.assertEquals(paymentTransaction.getTransactionExternalKey(), transactionExternalKey); + Assert.assertEquals(paymentTransaction.getPaymentExternalKey(), paymentExternalKey); } } From 4140dd923ac404380ae60317f6591ce617a982ba Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 12 Aug 2015 16:09:16 -0700 Subject: [PATCH 011/137] jaxrs: relax constraints for complete operations This addresses https://github.com/killbill/killbill/issues/370. and adds tests for https://github.com/killbill/killbill/issues/369. Signed-off-by: Pierre-Alexandre Meyer --- .../jaxrs/resources/PaymentResource.java | 82 ++++++++-- .../killbill/billing/jaxrs/TestPayment.java | 146 +++++++++++++++--- 2 files changed, 193 insertions(+), 35 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java index a96e3b0de4..95eed008ac 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java @@ -17,7 +17,9 @@ package org.killbill.billing.jaxrs.resources; +import java.math.BigDecimal; import java.net.URI; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,6 +58,7 @@ import org.killbill.billing.payment.api.PaymentApi; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.api.PaymentOptions; +import org.killbill.billing.payment.api.PaymentTransaction; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionType; import org.killbill.billing.util.api.AuditUserApi; @@ -69,7 +72,9 @@ import com.codahale.metrics.annotation.Timed; import com.google.common.base.Function; +import com.google.common.base.Predicate; import com.google.common.base.Strings; +import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableMap; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; @@ -270,39 +275,84 @@ private Response completeTransactionInternal(final PaymentTransactionJson json, final String comment, final UriInfo uriInfo, final HttpServletRequest request) throws PaymentApiException, AccountApiException { - verifyNonNullOrEmpty(json, "PaymentTransactionJson body should be specified"); - verifyNonNullOrEmpty(json.getTransactionType(), "PaymentTransactionJson transactionType needs to be set"); - if (paymentIdStr == null) { - verifyNonNullOrEmpty(json.getPaymentExternalKey(), "PaymentTransactionJson externalKey needs to be set"); - } - final Iterable pluginProperties = extractPluginProperties(pluginPropertiesString); final CallContext callContext = context.createContext(createdBy, reason, comment, request); - final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json.getPaymentExternalKey(), pluginProperties, callContext); + final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json == null ? null : json.getPaymentExternalKey(), pluginProperties, callContext); final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext); - final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency()); + final BigDecimal amount = json == null ? null : json.getAmount(); + final Currency currency = json == null || json.getCurrency() == null ? null : Currency.valueOf(json.getCurrency()); + + final TransactionType transactionType; + final String transactionExternalKey; + if (json != null && json.getTransactionExternalKey() != null && json.getTransactionType() != null) { + transactionType = TransactionType.valueOf(json.getTransactionType()); + transactionExternalKey = json.getTransactionExternalKey(); + } else if (json != null && json.getTransactionExternalKey() != null) { + final Collection paymentTransactionCandidates = Collections2.filter(initialPayment.getTransactions(), + new Predicate() { + @Override + public boolean apply(final PaymentTransaction input) { + return input.getExternalKey().equals(json.getTransactionExternalKey()); + } + }); + if (paymentTransactionCandidates.size() == 1) { + transactionType = paymentTransactionCandidates.iterator().next().getTransactionType(); + transactionExternalKey = json.getTransactionExternalKey(); + } else { + // Note: we could bit a bit smarter but keep the logic in the payment system + verifyNonNullOrEmpty(null, "PaymentTransactionJson transactionType needs to be set"); + // Never reached + return Response.status(Status.PRECONDITION_FAILED).build(); + } + } else if (json != null && json.getTransactionType() != null) { + final Collection paymentTransactionCandidates = Collections2.filter(initialPayment.getTransactions(), + new Predicate() { + @Override + public boolean apply(final PaymentTransaction input) { + return input.getTransactionType().toString().equals(json.getTransactionType()); + } + }); + if (paymentTransactionCandidates.size() == 1) { + transactionType = TransactionType.valueOf(json.getTransactionType()); + transactionExternalKey = paymentTransactionCandidates.iterator().next().getExternalKey(); + } else { + verifyNonNullOrEmpty(null, "PaymentTransactionJson externalKey needs to be set"); + // Never reached + return Response.status(Status.PRECONDITION_FAILED).build(); + } + } else if (initialPayment.getTransactions().size() == 1) { + final PaymentTransaction paymentTransaction = initialPayment.getTransactions().get(0); + transactionType = paymentTransaction.getTransactionType(); + transactionExternalKey = paymentTransaction.getExternalKey(); + } else { + verifyNonNullOrEmpty(null, "PaymentTransactionJson transactionType and externalKey need to be set"); + // Never reached + return Response.status(Status.PRECONDITION_FAILED).build(); + } - final TransactionType transactionType = TransactionType.valueOf(json.getTransactionType()); final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames); switch (transactionType) { case AUTHORIZE: - paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), json.getAmount(), currency, - json.getPaymentExternalKey(), json.getTransactionExternalKey(), + paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency, + initialPayment.getExternalKey(), transactionExternalKey, pluginProperties, paymentOptions, callContext); break; case PURCHASE: - paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), json.getAmount(), currency, - json.getPaymentExternalKey(), json.getTransactionExternalKey(), + paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency, + initialPayment.getExternalKey(), transactionExternalKey, pluginProperties, paymentOptions, callContext); break; case CREDIT: - paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), json.getAmount(), currency, - json.getPaymentExternalKey(), json.getTransactionExternalKey(), + paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency, + initialPayment.getExternalKey(), transactionExternalKey, pluginProperties, paymentOptions, callContext); break; + case REFUND: + paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency, + transactionExternalKey, pluginProperties, paymentOptions, callContext); + break; default: - // It looks like we need at least REFUND? See https://github.com/killbill/killbill/issues/371 return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " cannot be completed").build(); } return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", initialPayment.getId()); diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java index df635ae56b..9b7919b078 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java @@ -33,6 +33,8 @@ import org.killbill.billing.client.model.Payments; import org.killbill.billing.client.model.PluginProperty; import org.killbill.billing.payment.api.TransactionType; +import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; +import org.killbill.billing.payment.provider.MockPaymentProviderPlugin; import org.testng.Assert; import org.testng.annotations.Test; @@ -52,6 +54,99 @@ public void testCreateRetrievePayment() throws Exception { testCreateRetrievePayment(account, nonDefaultPaymentMethod.getPaymentMethodId(), UUID.randomUUID().toString(), 2); } + @Test(groups = "slow") + public void testCompletionForInitialTransaction() throws Exception { + final Account account = createAccountWithDefaultPaymentMethod(); + final UUID paymentMethodId = account.getPaymentMethodId(); + final BigDecimal amount = BigDecimal.TEN; + + final String pending = PaymentPluginStatus.PENDING.toString(); + final ImmutableMap pluginProperties = ImmutableMap.of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending); + + int paymentNb = 0; + for (final TransactionType transactionType : ImmutableList.of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) { + final BigDecimal authAmount = BigDecimal.ZERO; + final String paymentExternalKey = UUID.randomUUID().toString(); + final String authTransactionExternalKey = UUID.randomUUID().toString(); + paymentNb++; + + final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, authAmount, pluginProperties, paymentNb); + final PaymentTransaction authPaymentTransaction = initialPayment.getTransactions().get(0); + + // Complete operation: first, only specify the payment id + final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction(); + completeTransactionByPaymentId.setPaymentId(initialPayment.getPaymentId()); + final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment); + verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); + + // Second, only specify the payment external key + final PaymentTransaction completeTransactionByPaymentExternalKey = new PaymentTransaction(); + completeTransactionByPaymentExternalKey.setPaymentExternalKey(initialPayment.getPaymentExternalKey()); + final Payment completedPaymentByExternalKey = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment); + verifyPayment(account, paymentMethodId, completedPaymentByExternalKey, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); + + // Third, specify the payment id and transaction external key + final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction(); + completeTransactionWithTypeAndKey.setPaymentId(initialPayment.getPaymentId()); + completeTransactionWithTypeAndKey.setTransactionExternalKey(authPaymentTransaction.getTransactionExternalKey()); + final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment); + verifyPayment(account, paymentMethodId, completedPaymentByTypeAndKey, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); + } + } + + @Test(groups = "slow") + public void testCompletionForSubsequentTransaction() throws Exception { + final Account account = createAccountWithDefaultPaymentMethod(); + final UUID paymentMethodId = account.getPaymentMethodId(); + final String paymentExternalKey = UUID.randomUUID().toString(); + final String purchaseTransactionExternalKey = UUID.randomUUID().toString(); + final BigDecimal purchaseAmount = BigDecimal.TEN; + + // Create a successful purchase + final Payment authPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, TransactionType.PURCHASE, + "SUCCESS", purchaseAmount, BigDecimal.ZERO, ImmutableMap.of(), 1); + + final String pending = PaymentPluginStatus.PENDING.toString(); + final ImmutableMap pluginProperties = ImmutableMap.of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending); + + // Trigger a pending refund + final String refundTransactionExternalKey = UUID.randomUUID().toString(); + final PaymentTransaction refundTransaction = new PaymentTransaction(); + refundTransaction.setPaymentId(authPayment.getPaymentId()); + refundTransaction.setTransactionExternalKey(refundTransactionExternalKey); + refundTransaction.setAmount(purchaseAmount); + refundTransaction.setCurrency(authPayment.getCurrency()); + final Payment refundPayment = killBillClient.refundPayment(refundTransaction, pluginProperties, createdBy, reason, comment); + verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, refundPayment); + + // We cannot complete using just the payment id as JAX-RS doesn't know which transaction to complete + try { + final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction(); + completeTransactionByPaymentId.setPaymentId(refundPayment.getPaymentId()); + killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment); + Assert.fail(); + } catch (final KillBillClientException e) { + Assert.assertEquals(e.getMessage(), "PaymentTransactionJson transactionType and externalKey need to be set"); + } + + // We cannot complete using just the payment external key as JAX-RS doesn't know which transaction to complete + try { + final PaymentTransaction completeTransactionByPaymentExternalKey = new PaymentTransaction(); + completeTransactionByPaymentExternalKey.setPaymentExternalKey(refundPayment.getPaymentExternalKey()); + killBillClient.completePayment(completeTransactionByPaymentExternalKey, pluginProperties, createdBy, reason, comment); + Assert.fail(); + } catch (final KillBillClientException e) { + Assert.assertEquals(e.getMessage(), "PaymentTransactionJson transactionType and externalKey need to be set"); + } + + // Finally, it should work if we specify the payment id and transaction external key + final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction(); + completeTransactionWithTypeAndKey.setPaymentId(refundPayment.getPaymentId()); + completeTransactionWithTypeAndKey.setTransactionExternalKey(refundTransactionExternalKey); + final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, createdBy, reason, comment); + verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, completedPaymentByTypeAndKey); + } + @Test(groups = "slow") public void testComboAuthorization() throws Exception { final Account accountJson = getAccount(); @@ -81,14 +176,15 @@ public void testComboAuthorization() throws Exception { final String voidTransactionExternalKey = UUID.randomUUID().toString(); final Payment voidPayment = killBillClient.voidPayment(null, paymentExternalKey, voidTransactionExternalKey, ImmutableMap.of(), createdBy, reason, comment); verifyPaymentTransaction(accountJson, voidPayment.getPaymentId(), paymentExternalKey, voidPayment.getTransactions().get(1), - voidTransactionExternalKey, null, "VOID"); + voidTransactionExternalKey, null, "VOID", "SUCCESS"); } private void testCreateRetrievePayment(final Account account, @Nullable final UUID paymentMethodId, final String paymentExternalKey, final int paymentNb) throws Exception { // Authorization final String authTransactionExternalKey = UUID.randomUUID().toString(); - final Payment authPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, TransactionType.AUTHORIZE, ImmutableMap.of(), paymentNb); + final Payment authPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, TransactionType.AUTHORIZE, + "SUCCESS", BigDecimal.TEN, BigDecimal.TEN, ImmutableMap.of(), paymentNb); // Capture 1 final String capture1TransactionExternalKey = UUID.randomUUID().toString(); @@ -100,10 +196,10 @@ private void testCreateRetrievePayment(final Account account, @Nullable final UU captureTransaction.setTransactionExternalKey(capture1TransactionExternalKey); // captureAuthorization is using paymentId final Payment capturedPayment1 = killBillClient.captureAuthorization(captureTransaction, createdBy, reason, comment); - verifyPayment(account, paymentMethodId, capturedPayment1, paymentExternalKey, authTransactionExternalKey, - BigDecimal.TEN, BigDecimal.ONE, BigDecimal.ZERO, 2, paymentNb); + verifyPayment(account, paymentMethodId, capturedPayment1, paymentExternalKey, authTransactionExternalKey, "AUTHORIZE", "SUCCESS", + BigDecimal.TEN, BigDecimal.TEN, BigDecimal.ONE, BigDecimal.ZERO, 2, paymentNb); verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, capturedPayment1.getTransactions().get(1), - capture1TransactionExternalKey, captureTransaction.getAmount(), "CAPTURE"); + capture1TransactionExternalKey, captureTransaction.getAmount(), "CAPTURE", "SUCCESS"); // Capture 2 final String capture2TransactionExternalKey = UUID.randomUUID().toString(); @@ -111,10 +207,10 @@ private void testCreateRetrievePayment(final Account account, @Nullable final UU // captureAuthorization is using externalKey captureTransaction.setPaymentId(null); final Payment capturedPayment2 = killBillClient.captureAuthorization(captureTransaction, createdBy, reason, comment); - verifyPayment(account, paymentMethodId, capturedPayment2, paymentExternalKey, authTransactionExternalKey, - BigDecimal.TEN, new BigDecimal("2"), BigDecimal.ZERO, 3, paymentNb); + verifyPayment(account, paymentMethodId, capturedPayment2, paymentExternalKey, authTransactionExternalKey, "AUTHORIZE", "SUCCESS", + BigDecimal.TEN, BigDecimal.TEN, new BigDecimal("2"), BigDecimal.ZERO, 3, paymentNb); verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, capturedPayment2.getTransactions().get(2), - capture2TransactionExternalKey, captureTransaction.getAmount(), "CAPTURE"); + capture2TransactionExternalKey, captureTransaction.getAmount(), "CAPTURE", "SUCCESS"); // Refund final String refundTransactionExternalKey = UUID.randomUUID().toString(); @@ -125,10 +221,10 @@ private void testCreateRetrievePayment(final Account account, @Nullable final UU refundTransaction.setPaymentExternalKey(paymentExternalKey); refundTransaction.setTransactionExternalKey(refundTransactionExternalKey); final Payment refundPayment = killBillClient.refundPayment(refundTransaction, createdBy, reason, comment); - verifyPayment(account, paymentMethodId, refundPayment, paymentExternalKey, authTransactionExternalKey, - BigDecimal.TEN, new BigDecimal("2"), new BigDecimal("2"), 4, paymentNb); + verifyPayment(account, paymentMethodId, refundPayment, paymentExternalKey, authTransactionExternalKey, "AUTHORIZE", "SUCCESS", + BigDecimal.TEN, BigDecimal.TEN, new BigDecimal("2"), new BigDecimal("2"), 4, paymentNb); verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, refundPayment.getTransactions().get(3), - refundTransactionExternalKey, refundTransaction.getAmount(), "REFUND"); + refundTransactionExternalKey, refundTransaction.getAmount(), "REFUND", "SUCCESS"); } private Payment createVerifyTransaction(final Account account, @@ -136,17 +232,20 @@ private Payment createVerifyTransaction(final Account account, final String paymentExternalKey, final String transactionExternalKey, final TransactionType transactionType, + final String transactionStatus, + final BigDecimal transactionAmount, + final BigDecimal authAmount, final Map pluginProperties, final int paymentNb) throws KillBillClientException { final PaymentTransaction authTransaction = new PaymentTransaction(); - authTransaction.setAmount(BigDecimal.TEN); + authTransaction.setAmount(transactionAmount); authTransaction.setCurrency(account.getCurrency()); authTransaction.setPaymentExternalKey(paymentExternalKey); authTransaction.setTransactionExternalKey(transactionExternalKey); authTransaction.setTransactionType(transactionType.toString()); final Payment payment = killBillClient.createPayment(account.getAccountId(), paymentMethodId, authTransaction, pluginProperties, createdBy, reason, comment); - verifyPaymentNoTransaction(account, paymentMethodId, payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); + verifyPayment(account, paymentMethodId, payment, paymentExternalKey, transactionExternalKey, transactionType.toString(), transactionStatus, transactionAmount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); return payment; } @@ -174,14 +273,17 @@ private void verifyPayment(final Account account, @Nullable final UUID paymentMethodId, final Payment payment, final String paymentExternalKey, - final String authTransactionExternalKey, - final BigDecimal authAmount, + final String firstTransactionExternalKey, + final String firstTransactionType, + final String firstTransactionStatus, + final BigDecimal firstTransactionAmount, + final BigDecimal paymentAuthAmount, final BigDecimal capturedAmount, final BigDecimal refundedAmount, final int nbTransactions, final int paymentNb) throws KillBillClientException { - verifyPaymentNoTransaction(account, paymentMethodId, payment, paymentExternalKey, authAmount, capturedAmount, refundedAmount, nbTransactions, paymentNb); - verifyPaymentTransaction(account, payment.getPaymentId(), paymentExternalKey, payment.getTransactions().get(0), authTransactionExternalKey, authAmount, "AUTHORIZE"); + verifyPaymentNoTransaction(account, paymentMethodId, payment, paymentExternalKey, paymentAuthAmount, capturedAmount, refundedAmount, nbTransactions, paymentNb); + verifyPaymentTransaction(account, payment.getPaymentId(), paymentExternalKey, payment.getTransactions().get(0), firstTransactionExternalKey, firstTransactionAmount, firstTransactionType, firstTransactionStatus); } private void verifyPaymentNoTransaction(final Account account, @@ -222,11 +324,12 @@ private void verifyPaymentTransaction(final Account account, final PaymentTransaction paymentTransaction, final String transactionExternalKey, @Nullable final BigDecimal amount, - final String transactionType) { + final String transactionType, + final String transactionStatus) { Assert.assertEquals(paymentTransaction.getPaymentId(), paymentId); Assert.assertNotNull(paymentTransaction.getTransactionId()); Assert.assertEquals(paymentTransaction.getTransactionType(), transactionType); - Assert.assertEquals(paymentTransaction.getStatus(), "SUCCESS"); + Assert.assertEquals(paymentTransaction.getStatus(), transactionStatus); if (amount == null) { Assert.assertNull(paymentTransaction.getAmount()); Assert.assertNull(paymentTransaction.getCurrency()); @@ -237,4 +340,9 @@ private void verifyPaymentTransaction(final Account account, Assert.assertEquals(paymentTransaction.getTransactionExternalKey(), transactionExternalKey); Assert.assertEquals(paymentTransaction.getPaymentExternalKey(), paymentExternalKey); } + + private void verifyPaymentWithPendingRefund(final Account account, final UUID paymentMethodId, final String paymentExternalKey, final String authTransactionExternalKey, final BigDecimal purchaseAmount, final String refundTransactionExternalKey, final Payment refundPayment) throws KillBillClientException { + verifyPayment(account, paymentMethodId, refundPayment, paymentExternalKey, authTransactionExternalKey, "PURCHASE", "SUCCESS", purchaseAmount, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, 2, 1); + verifyPaymentTransaction(account, refundPayment.getPaymentId(), paymentExternalKey, refundPayment.getTransactions().get(1), refundTransactionExternalKey, purchaseAmount, "REFUND", "PENDING"); + } } From e6f7f1349a3e6c68a012a151762dcf65872e280b Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 12 Aug 2015 16:27:37 -0700 Subject: [PATCH 012/137] server: fix typos in TestPayment Signed-off-by: Pierre-Alexandre Meyer --- .../src/test/java/org/killbill/billing/jaxrs/TestPayment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java index 9b7919b078..ba0e7693c6 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java @@ -82,14 +82,14 @@ public void testCompletionForInitialTransaction() throws Exception { // Second, only specify the payment external key final PaymentTransaction completeTransactionByPaymentExternalKey = new PaymentTransaction(); completeTransactionByPaymentExternalKey.setPaymentExternalKey(initialPayment.getPaymentExternalKey()); - final Payment completedPaymentByExternalKey = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment); + final Payment completedPaymentByExternalKey = killBillClient.completePayment(completeTransactionByPaymentExternalKey, pluginProperties, createdBy, reason, comment); verifyPayment(account, paymentMethodId, completedPaymentByExternalKey, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); // Third, specify the payment id and transaction external key final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction(); completeTransactionWithTypeAndKey.setPaymentId(initialPayment.getPaymentId()); completeTransactionWithTypeAndKey.setTransactionExternalKey(authPaymentTransaction.getTransactionExternalKey()); - final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment); + final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, createdBy, reason, comment); verifyPayment(account, paymentMethodId, completedPaymentByTypeAndKey, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); } } From fa717061d1dad052ddfc70c1dbcdc7d093aa14f4 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 12 Aug 2015 16:39:46 -0700 Subject: [PATCH 013/137] jaxrs: allow complete operations by specifying the transaction id Signed-off-by: Pierre-Alexandre Meyer --- .../jaxrs/resources/PaymentResource.java | 17 ++++++++++++++++- .../org/killbill/billing/jaxrs/TestPayment.java | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java index 95eed008ac..e69e85cf35 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java @@ -285,7 +285,22 @@ private Response completeTransactionInternal(final PaymentTransactionJson json, final TransactionType transactionType; final String transactionExternalKey; - if (json != null && json.getTransactionExternalKey() != null && json.getTransactionType() != null) { + if (json != null && json.getTransactionId() != null) { + final Collection paymentTransactionCandidates = Collections2.filter(initialPayment.getTransactions(), + new Predicate() { + @Override + public boolean apply(final PaymentTransaction input) { + return input.getId().toString().equals(json.getTransactionId()); + } + }); + if (paymentTransactionCandidates.size() == 1) { + final PaymentTransaction paymentTransaction = paymentTransactionCandidates.iterator().next(); + transactionType = paymentTransaction.getTransactionType(); + transactionExternalKey = paymentTransaction.getExternalKey(); + } else { + return Response.status(Status.NOT_FOUND).build(); + } + } else if (json != null && json.getTransactionExternalKey() != null && json.getTransactionType() != null) { transactionType = TransactionType.valueOf(json.getTransactionType()); transactionExternalKey = json.getTransactionExternalKey(); } else if (json != null && json.getTransactionExternalKey() != null) { diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java index ba0e7693c6..1cfdb15050 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java @@ -91,6 +91,13 @@ public void testCompletionForInitialTransaction() throws Exception { completeTransactionWithTypeAndKey.setTransactionExternalKey(authPaymentTransaction.getTransactionExternalKey()); final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, createdBy, reason, comment); verifyPayment(account, paymentMethodId, completedPaymentByTypeAndKey, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); + + // Finally, specify the payment id and transaction id + final PaymentTransaction completeTransactionWithTypeAndId = new PaymentTransaction(); + completeTransactionWithTypeAndId.setPaymentId(initialPayment.getPaymentId()); + completeTransactionWithTypeAndId.setTransactionId(authPaymentTransaction.getTransactionId()); + final Payment completedPaymentByTypeAndId = killBillClient.completePayment(completeTransactionWithTypeAndId, pluginProperties, createdBy, reason, comment); + verifyPayment(account, paymentMethodId, completedPaymentByTypeAndId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb); } } @@ -145,6 +152,13 @@ public void testCompletionForSubsequentTransaction() throws Exception { completeTransactionWithTypeAndKey.setTransactionExternalKey(refundTransactionExternalKey); final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, createdBy, reason, comment); verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, completedPaymentByTypeAndKey); + + // Also, it should work if we specify the payment id and transaction id + final PaymentTransaction completeTransactionWithTypeAndId = new PaymentTransaction(); + completeTransactionWithTypeAndId.setPaymentId(refundPayment.getPaymentId()); + completeTransactionWithTypeAndId.setTransactionId(refundPayment.getTransactions().get(1).getTransactionId()); + final Payment completedPaymentByTypeAndId = killBillClient.completePayment(completeTransactionWithTypeAndId, pluginProperties, createdBy, reason, comment); + verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, completedPaymentByTypeAndId); } @Test(groups = "slow") From 69aee775b22fcc9b61c7f472a4d8f6394e23f7bc Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 12 Aug 2015 17:18:18 -0700 Subject: [PATCH 014/137] Implementation for buildFormDescriptorWithPaymentControl and processNotificationWithPaymentControl. See #329 --- .../billing/payment/api/DefaultApiBase.java | 116 ++++++ .../payment/api/DefaultPaymentApi.java | 74 +--- .../payment/api/DefaultPaymentGatewayApi.java | 85 +++- .../PluginControlPaymentAutomatonRunner.java | 22 +- .../sm/control/AuthorizeControlOperation.java | 4 +- .../sm/control/CaptureControlOperation.java | 4 +- .../control/ChargebackControlOperation.java | 4 +- .../control/CompletionControlOperation.java | 11 +- .../core/sm/control/ControlPluginRunner.java | 379 ++++++++++++++++++ .../sm/control/CreditControlOperation.java | 4 +- .../sm/control/OperationControlCallback.java | 285 +++---------- .../sm/control/PurchaseControlOperation.java | 4 +- .../sm/control/RefundControlOperation.java | 4 +- .../core/sm/control/VoidControlOperation.java | 4 +- .../billing/payment/glue/PaymentModule.java | 3 + .../retry/DefaultFailureCallResult.java | 12 +- .../DefaultOnSuccessPaymentControlResult.java | 12 +- .../payment/api/TestPaymentGatewayApi.java | 4 + .../MockRetryAuthorizeOperationCallback.java | 5 +- .../MockRetryablePaymentAutomatonRunner.java | 5 +- .../payment/core/sm/TestRetryablePayment.java | 6 +- 21 files changed, 718 insertions(+), 329 deletions(-) create mode 100644 payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java create mode 100644 payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java create mode 100644 payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java new file mode 100644 index 0000000000..cd916d9c1d --- /dev/null +++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java @@ -0,0 +1,116 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.payment.api; + +import java.math.BigDecimal; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.killbill.billing.ErrorCode; +import org.killbill.billing.account.api.Account; +import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; + +public class DefaultApiBase { + + private static final Logger log = LoggerFactory.getLogger(DefaultApiBase.class); + + private final PaymentConfig paymentConfig; + + public DefaultApiBase(final PaymentConfig paymentConfig) { + this.paymentConfig = paymentConfig; + } + + protected void logAPICall(final String transactionType, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey) { + if (log.isInfoEnabled()) { + final StringBuilder logLine = new StringBuilder(); + logLine.append("PaymentApi : ") + .append(transactionType) + .append(", account = ") + .append(account.getId()); + if (paymentMethodId != null) { + logLine.append(", paymentMethodId = ") + .append(paymentMethodId); + } + if (paymentExternalKey != null) { + logLine.append(", paymentExternalKey = ") + .append(paymentExternalKey); + } + if (paymentTransactionExternalKey != null) { + logLine.append(", paymentTransactionExternalKey = ") + .append(paymentTransactionExternalKey); + } + if (paymentId != null) { + logLine.append(", paymentId = ") + .append(paymentId); + } + if (transactionId != null) { + logLine.append(", transactionId = ") + .append(transactionId); + } + if (amount != null) { + logLine.append(", amount = ") + .append(amount); + } + if (currency != null) { + logLine.append(", currency = ") + .append(currency); + } + log.info(logLine.toString()); + } + } + + protected List toPaymentControlPluginNames(final PaymentOptions paymentOptions) { + // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase) + if (paymentConfig.getPaymentControlPluginNames() != null && + paymentOptions.getPaymentControlPluginNames() != null && + paymentOptions.getPaymentControlPluginNames().size() == 1 && + InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentOptions.getPaymentControlPluginNames().get(0))) { + final List paymentControlPluginNames = new LinkedList(paymentOptions.getPaymentControlPluginNames()); + paymentControlPluginNames.addAll(paymentConfig.getPaymentControlPluginNames()); + return paymentControlPluginNames; + } else if (paymentOptions.getPaymentControlPluginNames() != null && !paymentOptions.getPaymentControlPluginNames().isEmpty()) { + return paymentOptions.getPaymentControlPluginNames(); + } else if (paymentConfig.getPaymentControlPluginNames() != null && !paymentConfig.getPaymentControlPluginNames().isEmpty()) { + return paymentConfig.getPaymentControlPluginNames(); + } else { + return ImmutableList.of(); + } + } + + protected void checkNotNullParameter(final Object parameter, final String parameterName) throws PaymentApiException { + if (parameter == null) { + throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null"); + } + } + + protected void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException { + if (amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0"); + } + } + +} diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java index 1e2d91d217..656f830c03 100644 --- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java @@ -44,7 +44,7 @@ import com.google.common.collect.ImmutableList; -public class DefaultPaymentApi implements PaymentApi { +public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi { private static final boolean SHOULD_LOCK_ACCOUNT = true; private static final boolean IS_API_PAYMENT = true; @@ -52,7 +52,6 @@ public class DefaultPaymentApi implements PaymentApi { private static final Logger log = LoggerFactory.getLogger(DefaultPaymentApi.class); - private final PaymentConfig paymentConfig; private final PaymentProcessor paymentProcessor; private final PaymentMethodProcessor paymentMethodProcessor; private final PluginControlPaymentProcessor pluginControlPaymentProcessor; @@ -60,7 +59,7 @@ public class DefaultPaymentApi implements PaymentApi { @Inject public DefaultPaymentApi(final PaymentConfig paymentConfig, final PaymentProcessor paymentProcessor, final PaymentMethodProcessor paymentMethodProcessor, final PluginControlPaymentProcessor pluginControlPaymentProcessor, final InternalCallContextFactory internalCallContextFactory) { - this.paymentConfig = paymentConfig; + super(paymentConfig); this.paymentProcessor = paymentProcessor; this.paymentMethodProcessor = paymentMethodProcessor; this.pluginControlPaymentProcessor = pluginControlPaymentProcessor; @@ -498,73 +497,4 @@ public List refreshPaymentMethods(final Account account, final It return paymentMethods; } - - private void logAPICall(final String transactionType, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey) { - if (log.isInfoEnabled()) { - final StringBuilder logLine = new StringBuilder(); - logLine.append("PaymentApi : ") - .append(transactionType) - .append(", account = ") - .append(account.getId()); - if (paymentMethodId != null) { - logLine.append(", paymentMethodId = ") - .append(paymentMethodId); - } - if (paymentExternalKey != null) { - logLine.append(", paymentExternalKey = ") - .append(paymentExternalKey); - } - if (paymentTransactionExternalKey != null) { - logLine.append(", paymentTransactionExternalKey = ") - .append(paymentTransactionExternalKey); - } - if (paymentId != null) { - logLine.append(", paymentId = ") - .append(paymentId); - } - if (transactionId != null) { - logLine.append(", transactionId = ") - .append(transactionId); - } - if (amount != null) { - logLine.append(", amount = ") - .append(amount); - } - if (currency != null) { - logLine.append(", currency = ") - .append(currency); - } - log.info(logLine.toString()); - } - } - - private List toPaymentControlPluginNames(final PaymentOptions paymentOptions) { - // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase) - if (paymentConfig.getPaymentControlPluginNames() != null && - paymentOptions.getPaymentControlPluginNames() != null && - paymentOptions.getPaymentControlPluginNames().size() == 1 && - InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentOptions.getPaymentControlPluginNames().get(0))) { - final List paymentControlPluginNames = new LinkedList(paymentOptions.getPaymentControlPluginNames()); - paymentControlPluginNames.addAll(paymentConfig.getPaymentControlPluginNames()); - return paymentControlPluginNames; - } else if (paymentOptions.getPaymentControlPluginNames() != null && !paymentOptions.getPaymentControlPluginNames().isEmpty()) { - return paymentOptions.getPaymentControlPluginNames(); - } else if (paymentConfig.getPaymentControlPluginNames() != null && !paymentConfig.getPaymentControlPluginNames().isEmpty()) { - return paymentConfig.getPaymentControlPluginNames(); - } else { - return ImmutableList.of(); - } - } - - private void checkNotNullParameter(final Object parameter, final String parameterName) throws PaymentApiException { - if (parameter == null) { - throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null"); - } - } - - private void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException { - if (amount.compareTo(BigDecimal.ZERO) <= 0) { - throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0"); - } - } } diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java index c9adbaf313..ae10edb4b6 100644 --- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java @@ -17,6 +17,7 @@ package org.killbill.billing.payment.api; +import java.util.List; import java.util.UUID; import javax.annotation.Nullable; @@ -24,20 +25,32 @@ import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; +import org.killbill.billing.control.plugin.api.HPPType; +import org.killbill.billing.control.plugin.api.PaymentApiType; +import org.killbill.billing.control.plugin.api.PaymentControlApiException; +import org.killbill.billing.control.plugin.api.PriorPaymentControlResult; import org.killbill.billing.payment.core.PaymentGatewayProcessor; +import org.killbill.billing.payment.core.sm.control.ControlPluginRunner; import org.killbill.billing.payment.plugin.api.GatewayNotification; import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.InternalCallContextFactory; +import org.killbill.billing.util.config.PaymentConfig; -public class DefaultPaymentGatewayApi implements PaymentGatewayApi { +public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentGatewayApi { private final PaymentGatewayProcessor paymentGatewayProcessor; + private final ControlPluginRunner controlPluginRunner; private final InternalCallContextFactory internalCallContextFactory; @Inject - public DefaultPaymentGatewayApi(final PaymentGatewayProcessor paymentGatewayProcessor, final InternalCallContextFactory internalCallContextFactory) { + public DefaultPaymentGatewayApi(final PaymentConfig paymentConfig, + final PaymentGatewayProcessor paymentGatewayProcessor, + final ControlPluginRunner controlPluginRunner, + final InternalCallContextFactory internalCallContextFactory) { + super(paymentConfig); this.paymentGatewayProcessor = paymentGatewayProcessor; + this.controlPluginRunner = controlPluginRunner; this.internalCallContextFactory = internalCallContextFactory; } @@ -53,8 +66,14 @@ public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account } @Override - public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, final UUID uuid, final Iterable iterable, final Iterable iterable1, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException { - throw new IllegalStateException("Not implemented"); + public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, final Iterable customFields, final Iterable properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException { + + return executeWithPaymentControl(account, paymentMethodId, properties, paymentOptions, callContext, new WithPaymentControlCallback() { + @Override + public HostedPaymentPageFormDescriptor doPaymentGatewayApiOperation(final Iterable adjustedPluginProperties) throws PaymentApiException { + return buildFormDescriptor(account, paymentMethodId, customFields, adjustedPluginProperties, callContext); + } + }); } @Override @@ -63,7 +82,61 @@ public GatewayNotification processNotification(final String notification, final } @Override - public GatewayNotification processNotificationWithPaymentControl(final String s, final String s1, final Iterable iterable, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException { - throw new IllegalStateException("Not implemented"); + public GatewayNotification processNotificationWithPaymentControl(final String notification, final String pluginName, final Iterable properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException { + return executeWithPaymentControl(null, null, properties, paymentOptions, callContext, new WithPaymentControlCallback() { + @Override + public GatewayNotification doPaymentGatewayApiOperation(final Iterable adjustedPluginProperties) throws PaymentApiException { + return processNotification(notification, pluginName, adjustedPluginProperties, callContext); + } + }); + } + + + private interface WithPaymentControlCallback { + T doPaymentGatewayApiOperation(final Iterable adjustedPluginProperties) throws PaymentApiException; + } + + private T executeWithPaymentControl(@Nullable final Account account, + @Nullable final UUID paymentMethodId, + final Iterable properties, + final PaymentOptions paymentOptions, + final CallContext callContext, + final WithPaymentControlCallback callback) throws PaymentApiException { + + final List paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions); + if (paymentControlPluginNames.isEmpty()) { + return callback.doPaymentGatewayApiOperation(properties); + } + + final PriorPaymentControlResult priorCallResult; + try { + priorCallResult = controlPluginRunner.executePluginPriorCalls(account, + paymentMethodId, + null, null, null, null, + PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR, + null, null, true, paymentControlPluginNames, properties, callContext); + + } catch (final PaymentControlApiException e) { + throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e); + } + + try { + final T result = callback.doPaymentGatewayApiOperation(priorCallResult.getAdjustedPluginProperties()); + controlPluginRunner.executePluginOnSuccessCalls(account, + paymentMethodId, + null, null, null, null, + PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR, + null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext); + return result; + } catch (final PaymentApiException e) { + controlPluginRunner.executePluginOnFailureCalls(account, + paymentMethodId, + null, null, null, null, + PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR, + null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext); + + throw e; + + } } } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java index 9c1dbc70bc..f3ca0cad79 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java @@ -47,6 +47,7 @@ import org.killbill.billing.payment.core.sm.control.CaptureControlOperation; import org.killbill.billing.payment.core.sm.control.ChargebackControlOperation; import org.killbill.billing.payment.core.sm.control.CompletionControlOperation; +import org.killbill.billing.payment.core.sm.control.ControlPluginRunner; import org.killbill.billing.payment.core.sm.control.CreditControlOperation; import org.killbill.billing.payment.core.sm.control.DefaultControlCompleted; import org.killbill.billing.payment.core.sm.control.DefaultControlInitiated; @@ -78,16 +79,19 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner private final PaymentProcessor paymentProcessor; private final RetryServiceScheduler retryServiceScheduler; private final PaymentControlStateMachineHelper paymentControlStateMachineHelper; + private final ControlPluginRunner controlPluginRunner; @Inject public PluginControlPaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration pluginRegistry, final OSGIServiceRegistration paymentControlPluginRegistry, final Clock clock, final PaymentProcessor paymentProcessor, @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, - final PaymentConfig paymentConfig, @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper, final PersistentBus eventBus) { + final PaymentConfig paymentConfig, @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper, + final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) { super(stateMachineConfig, paymentConfig, paymentDao, locker, pluginRegistry, clock, executor, eventBus, paymentSMHelper); this.paymentProcessor = paymentProcessor; this.paymentControlPluginRegistry = paymentControlPluginRegistry; this.retryServiceScheduler = retryServiceScheduler; this.paymentControlStateMachineHelper = paymentControlStateMachineHelper; + this.controlPluginRunner = controlPluginRunner; } public Payment run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId, @@ -132,7 +136,7 @@ public Payment run(final State state, final boolean isApiPayment, final Transact public Payment completeRun(final PaymentStateControlContext paymentStateContext) throws PaymentApiException { try { - final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); final LeavingStateCallback leavingStateCallback = new NoopControlInitiated(); final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler); @@ -165,25 +169,25 @@ OperationCallback createOperationCallback(final TransactionType transactionType, final OperationCallback callback; switch (transactionType) { case AUTHORIZE: - callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); break; case CAPTURE: - callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); break; case PURCHASE: - callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); break; case VOID: - callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); break; case CREDIT: - callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); break; case REFUND: - callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); break; case CHARGEBACK: - callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); break; default: throw new IllegalStateException("Unsupported transaction type " + transactionType); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java index 96344610b2..0459ad2e5a 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java @@ -27,8 +27,8 @@ public class AuthorizeControlOperation extends OperationControlCallback { - public AuthorizeControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration paymentControlPluginRegistry) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + public AuthorizeControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java index 2d325c46fb..2b76885521 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java @@ -27,8 +27,8 @@ public class CaptureControlOperation extends OperationControlCallback { - public CaptureControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration paymentControlPluginRegistry) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + public CaptureControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java index 3f47fdcb74..07b53442fe 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java @@ -28,8 +28,8 @@ public class ChargebackControlOperation extends OperationControlCallback { - public ChargebackControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration paymentControlPluginRegistry) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + public ChargebackControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java index d6369e783b..6f3e300244 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java @@ -19,11 +19,13 @@ import org.killbill.automaton.OperationException; import org.killbill.automaton.OperationResult; +import org.killbill.billing.control.plugin.api.PaymentApiType; import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.Payment; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback; +import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext; import org.killbill.billing.payment.dao.PaymentTransactionModelDao; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; @@ -36,8 +38,11 @@ // public class CompletionControlOperation extends OperationControlCallback { - public CompletionControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration retryPluginRegistry) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry); + public CompletionControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); } @Override @@ -54,7 +59,9 @@ public PluginDispatcherReturnType doOperation() throws Operatio paymentStateContext.getPaymentExternalKey(), transaction.getId(), paymentStateContext.getPaymentTransactionExternalKey(), + PaymentApiType.PAYMENT_TRANSACTION, paymentStateContext.getTransactionType(), + null, transaction.getAmount(), transaction.getCurrency(), transaction.getProcessedAmount(), diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java new file mode 100644 index 0000000000..da94cfdf33 --- /dev/null +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java @@ -0,0 +1,379 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.payment.core.sm.control; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import org.joda.time.DateTime; +import org.killbill.billing.account.api.Account; +import org.killbill.billing.callcontext.DefaultCallContext; +import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.control.plugin.api.HPPType; +import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult; +import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult; +import org.killbill.billing.control.plugin.api.PaymentApiType; +import org.killbill.billing.control.plugin.api.PaymentControlApiException; +import org.killbill.billing.control.plugin.api.PaymentControlContext; +import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.control.plugin.api.PriorPaymentControlResult; +import org.killbill.billing.osgi.api.OSGIServiceRegistration; +import org.killbill.billing.payment.api.PluginProperty; +import org.killbill.billing.payment.api.TransactionType; +import org.killbill.billing.payment.retry.DefaultFailureCallResult; +import org.killbill.billing.payment.retry.DefaultOnSuccessPaymentControlResult; +import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult; +import org.killbill.billing.util.callcontext.CallContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ControlPluginRunner { + + private static final Logger log = LoggerFactory.getLogger(ControlPluginRunner.class); + + private final OSGIServiceRegistration paymentControlPluginRegistry; + + @Inject + public ControlPluginRunner(final OSGIServiceRegistration paymentControlPluginRegistry) { + this.paymentControlPluginRegistry = paymentControlPluginRegistry; + } + + public PriorPaymentControlResult executePluginPriorCalls(final Account account, + final UUID paymentMethodId, + final UUID paymentAttemptId, + final UUID paymentId, + final String paymentExternalKey, + final String paymentTransactionExternalKey, + final PaymentApiType paymentApiType, + final TransactionType transactionType, + final HPPType hppType, + final BigDecimal amount, + final Currency currency, + final boolean isApiPayment, + final List paymentControlPluginNames, + final Iterable pluginProperties, + final CallContext callContext) throws PaymentControlApiException { + // Return as soon as the first plugin aborts, or the last result for the last plugin + PriorPaymentControlResult prevResult = null; + + // Those values are adjusted prior each call with the result of what previous call to plugin returned + Iterable inputPluginProperties = pluginProperties; + PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account, + paymentMethodId, + paymentAttemptId, + paymentId, + paymentExternalKey, + paymentTransactionExternalKey, + paymentApiType, + transactionType, + hppType, + amount, + currency, + isApiPayment, + callContext); + + for (final String pluginName : paymentControlPluginNames) { + final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName); + if (plugin == null) { + // First call to plugin, we log warn, if plugin is not registered + log.warn("Skipping unknown payment control plugin {} when fetching results", pluginName); + continue; + } + prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties); + if (prevResult.getAdjustedPluginProperties() != null) { + inputPluginProperties = prevResult.getAdjustedPluginProperties(); + } + if (prevResult.isAborted()) { + break; + } + inputPaymentControlContext = new DefaultPaymentControlContext(account, + prevResult.getAdjustedPaymentMethodId() != null ? prevResult.getAdjustedPaymentMethodId() : paymentMethodId, + paymentAttemptId, + paymentId, + paymentExternalKey, + paymentTransactionExternalKey, + paymentApiType, + transactionType, + hppType, + prevResult.getAdjustedAmount() != null ? prevResult.getAdjustedAmount() : amount, + prevResult.getAdjustedCurrency() != null ? prevResult.getAdjustedCurrency() : currency, + isApiPayment, + callContext); + } + // Rebuild latest result to include inputPluginProperties + prevResult = new DefaultPriorPaymentControlResult(prevResult, inputPluginProperties); + return prevResult; + } + + + public OnSuccessPaymentControlResult executePluginOnSuccessCalls(final Account account, + final UUID paymentMethodId, + final UUID paymentAttemptId, + final UUID paymentId, + final String paymentExternalKey, + final String paymentTransactionExternalKey, + final PaymentApiType paymentApiType, + final TransactionType transactionType, + final HPPType hppType, + final BigDecimal amount, + final Currency currency, + final boolean isApiPayment, + final List paymentControlPluginNames, + final Iterable pluginProperties, + final CallContext callContext) { + + final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account, + paymentMethodId, + paymentAttemptId, + paymentId, + paymentExternalKey, + paymentTransactionExternalKey, + paymentApiType, + transactionType, + hppType, + amount, + currency, + isApiPayment, + callContext); + Iterable inputPluginProperties = pluginProperties; + for (final String pluginName : paymentControlPluginNames) { + final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName); + if (plugin != null) { + try { + final OnSuccessPaymentControlResult result = plugin.onSuccessCall(inputPaymentControlContext, inputPluginProperties); + if (result.getAdjustedPluginProperties() != null) { + inputPluginProperties = result.getAdjustedPluginProperties(); + } + // Exceptions from the control plugins are ignored (and logged) because the semantics on what to do are undefined. + } catch (final PaymentControlApiException e) { + log.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + inputPaymentControlContext.getPaymentExternalKey(), e); + } catch (final RuntimeException e) { + log.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + inputPaymentControlContext.getPaymentExternalKey(), e); + } + } + } + return new DefaultOnSuccessPaymentControlResult(inputPluginProperties); + } + + + public OnFailurePaymentControlResult executePluginOnFailureCalls(final Account account, + final UUID paymentMethodId, + final UUID paymentAttemptId, + final UUID paymentId, + final String paymentExternalKey, + final String paymentTransactionExternalKey, + final PaymentApiType paymentApiType, + final TransactionType transactionType, + final HPPType hppType, + final BigDecimal amount, + final Currency currency, + final boolean isApiPayment, + final List paymentControlPluginNames, + final Iterable pluginProperties, + final CallContext callContext) { + + final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account, + paymentMethodId, + paymentAttemptId, + paymentId, + paymentExternalKey, + paymentTransactionExternalKey, + paymentApiType, + transactionType, + hppType, + amount, + currency, + isApiPayment, + callContext); + + DateTime candidate = null; + Iterable inputPluginProperties = pluginProperties; + + for (final String pluginName : paymentControlPluginNames) { + final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName); + if (plugin != null) { + try { + final OnFailurePaymentControlResult result = plugin.onFailureCall(inputPaymentControlContext, inputPluginProperties); + if (candidate == null) { + candidate = result.getNextRetryDate(); + } else if (result.getNextRetryDate() != null) { + candidate = candidate.compareTo(result.getNextRetryDate()) > 0 ? result.getNextRetryDate() : candidate; + } + + if (result.getAdjustedPluginProperties() != null) { + inputPluginProperties = result.getAdjustedPluginProperties(); + } + + } catch (final PaymentControlApiException e) { + log.warn("Plugin " + pluginName + " failed to return next retryDate for payment " + inputPaymentControlContext.getPaymentExternalKey(), e); + return new DefaultFailureCallResult(candidate, inputPluginProperties); + } + } + } + return new DefaultFailureCallResult(candidate, inputPluginProperties); + } + + + + + public static class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext { + + private final Account account; + private final UUID paymentMethodId; + private final UUID attemptId; + private final UUID paymentId; + private final String paymentExternalKey; + private final UUID transactionId; + private final String transactionExternalKey; + private final PaymentApiType paymentApiType; + private final HPPType hppType; + private final TransactionType transactionType; + private final BigDecimal amount; + private final Currency currency; + private final BigDecimal processedAmount; + private final Currency processedCurrency; + private final boolean isApiPayment; + + public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey, + final PaymentApiType paymentApiType, final TransactionType transactionType, final HPPType hppType, final BigDecimal amount, final Currency currency, + final boolean isApiPayment, final CallContext callContext) { + this(account, paymentMethodId, attemptId, paymentId, paymentExternalKey, null, transactionExternalKey, paymentApiType, transactionType, hppType, amount, currency, null, null, isApiPayment, callContext); + } + + public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey, + final PaymentApiType paymentApiType, final TransactionType transactionType, final HPPType hppType, + final BigDecimal amount, final Currency currency, @Nullable final BigDecimal processedAmount, @Nullable final Currency processedCurrency, final boolean isApiPayment, final CallContext callContext) { + super(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate()); + this.account = account; + this.paymentMethodId = paymentMethodId; + this.attemptId = attemptId; + this.paymentId = paymentId; + this.paymentExternalKey = paymentExternalKey; + this.transactionId = transactionId; + this.transactionExternalKey = transactionExternalKey; + this.paymentApiType = paymentApiType; + this.hppType = hppType; + this.transactionType = transactionType; + this.amount = amount; + this.currency = currency; + this.processedAmount = processedAmount; + this.processedCurrency = processedCurrency; + this.isApiPayment = isApiPayment; + } + + @Override + public UUID getAccountId() { + return account.getId(); + } + + @Override + public String getPaymentExternalKey() { + return paymentExternalKey; + } + + @Override + public String getTransactionExternalKey() { + return transactionExternalKey; + } + + @Override + public PaymentApiType getPaymentApiType() { + return paymentApiType; + } + + @Override + public TransactionType getTransactionType() { + return transactionType; + } + + @Override + public HPPType getHPPType() { + return hppType; + } + + @Override + public BigDecimal getAmount() { + return amount; + } + + @Override + public Currency getCurrency() { + return currency; + } + + @Override + public UUID getPaymentMethodId() { + return paymentMethodId; + } + + @Override + public UUID getPaymentId() { + return paymentId; + } + + @Override + public UUID getAttemptPaymentId() { + return attemptId; + } + + @Override + public BigDecimal getProcessedAmount() { + return processedAmount; + } + + @Override + public Currency getProcessedCurrency() { + return processedCurrency; + } + + @Override + public boolean isApiPayment() { + return isApiPayment; + } + + public UUID getTransactionId() { + return transactionId; + } + + @Override + public String toString() { + return "DefaultPaymentControlContext{" + + "account=" + account + + ", paymentMethodId=" + paymentMethodId + + ", attemptId=" + attemptId + + ", paymentId=" + paymentId + + ", paymentExternalKey='" + paymentExternalKey + '\'' + + ", transactionId=" + transactionId + + ", transactionExternalKey='" + transactionExternalKey + '\'' + + ", paymentApiType=" + paymentApiType + + ", hppType=" + hppType + + ", transactionType=" + transactionType + + ", amount=" + amount + + ", currency=" + currency + + ", processedAmount=" + processedAmount + + ", processedCurrency=" + processedCurrency + + ", isApiPayment=" + isApiPayment + + '}'; + } + } + +} diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java index d0db2109dc..1a071fa549 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java @@ -27,8 +27,8 @@ public class CreditControlOperation extends OperationControlCallback { - public CreditControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration paymentControlPluginRegistry) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + public CreditControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java index e1c632fb04..f2458e8b3c 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java @@ -17,9 +17,7 @@ package org.killbill.billing.payment.core.sm.control; -import java.math.BigDecimal; import java.util.List; -import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -29,30 +27,24 @@ import org.killbill.automaton.Operation.OperationCallback; import org.killbill.automaton.OperationException; import org.killbill.automaton.OperationResult; -import org.killbill.billing.account.api.Account; -import org.killbill.billing.callcontext.DefaultCallContext; -import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult; import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult; +import org.killbill.billing.control.plugin.api.PaymentApiType; import org.killbill.billing.control.plugin.api.PaymentControlApiException; import org.killbill.billing.control.plugin.api.PaymentControlContext; -import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.control.plugin.api.PriorPaymentControlResult; -import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.Payment; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.api.PaymentTransaction; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionStatus; -import org.killbill.billing.payment.api.TransactionType; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback; import org.killbill.billing.payment.core.sm.OperationCallbackBase; import org.killbill.billing.payment.core.sm.PaymentStateContext; +import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; -import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult; -import org.killbill.billing.util.callcontext.CallContext; import org.killbill.commons.locker.GlobalLocker; import org.killbill.commons.locker.LockFailedException; import org.slf4j.Logger; @@ -62,15 +54,20 @@ public abstract class OperationControlCallback extends OperationCallbackBase implements OperationCallback { + private static final Logger logger = LoggerFactory.getLogger(OperationControlCallback.class); + protected final PaymentProcessor paymentProcessor; protected final PaymentStateControlContext paymentStateControlContext; - private final OSGIServiceRegistration paymentControlPluginRegistry; - private final Logger logger = LoggerFactory.getLogger(OperationControlCallback.class); + private final ControlPluginRunner controlPluginRunner; - protected OperationControlCallback(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration retryPluginRegistry) { + protected OperationControlCallback(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { super(locker, paymentPluginDispatcher, paymentStateContext); this.paymentProcessor = paymentProcessor; - this.paymentControlPluginRegistry = retryPluginRegistry; + this.controlPluginRunner = controlPluginRunner; this.paymentStateControlContext = paymentStateContext; } @@ -91,7 +88,9 @@ public PluginDispatcherReturnType doOperation() throws Operatio paymentStateContext.getPaymentId(), paymentStateContext.getPaymentExternalKey(), paymentStateContext.getPaymentTransactionExternalKey(), + PaymentApiType.PAYMENT_TRANSACTION, paymentStateContext.getTransactionType(), + null, paymentStateContext.getAmount(), paymentStateContext.getCurrency(), paymentStateControlContext.isApiPayment(), @@ -124,7 +123,9 @@ public PluginDispatcherReturnType doOperation() throws Operatio result.getExternalKey(), transaction.getId(), paymentStateContext.getPaymentTransactionExternalKey(), + PaymentApiType.PAYMENT_TRANSACTION, paymentStateContext.getTransactionType(), + null, transaction.getAmount(), transaction.getCurrency(), transaction.getProcessedAmount(), @@ -177,67 +178,45 @@ private OperationResult getOperationResultOnException(final PaymentStateContext } private PriorPaymentControlResult executePluginPriorCalls(final List paymentControlPluginNames, final PaymentControlContext paymentControlContextArg) throws PaymentControlApiException { - // Return as soon as the first plugin aborts, or the last result for the last plugin - PriorPaymentControlResult prevResult = null; - - // Those values are adjusted prior each call with the result of what previous call to plugin returned - PaymentControlContext inputPaymentControlContext = paymentControlContextArg; - Iterable inputPluginProperties = paymentStateContext.getProperties(); - - for (final String pluginName : paymentControlPluginNames) { - final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName); - if (plugin == null) { - // First call to plugin, we log warn, if plugin is not registered - logger.warn("Skipping unknown payment control plugin {} when fetching results", pluginName); - continue; - } - prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties); - if (prevResult.getAdjustedPluginProperties() != null) { - inputPluginProperties = prevResult.getAdjustedPluginProperties(); - } - if (prevResult.isAborted()) { - break; - } - inputPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(), - prevResult.getAdjustedPaymentMethodId() != null ? prevResult.getAdjustedPaymentMethodId() : inputPaymentControlContext.getPaymentMethodId(), - paymentStateControlContext.getAttemptId(), - paymentStateContext.getPaymentId(), - paymentStateContext.getPaymentExternalKey(), - paymentStateContext.getPaymentTransactionExternalKey(), - paymentStateContext.getTransactionType(), - prevResult.getAdjustedAmount() != null ? prevResult.getAdjustedAmount() : inputPaymentControlContext.getAmount(), - prevResult.getAdjustedCurrency() != null ? prevResult.getAdjustedCurrency() : inputPaymentControlContext.getCurrency(), - paymentStateControlContext.isApiPayment(), - paymentStateContext.getCallContext()); - } - // Rebuild latest result to include inputPluginProperties - prevResult = new DefaultPriorPaymentControlResult(prevResult, inputPluginProperties); - // Adjust context with all values if necessary - adjustStateContextForPriorCall(paymentStateContext, prevResult); - return prevResult; + final PriorPaymentControlResult result = controlPluginRunner.executePluginPriorCalls(paymentStateContext.getAccount(), + paymentControlContextArg.getPaymentMethodId(), + paymentStateControlContext.getAttemptId(), + paymentStateContext.getPaymentId(), + paymentStateContext.getPaymentExternalKey(), + paymentStateContext.getPaymentTransactionExternalKey(), + PaymentApiType.PAYMENT_TRANSACTION, + paymentStateContext.getTransactionType(), + null, + paymentControlContextArg.getAmount(), + paymentControlContextArg.getCurrency(), + paymentStateControlContext.isApiPayment(), + paymentControlPluginNames, + paymentStateContext.getProperties(), + paymentStateContext.getCallContext()); + + adjustStateContextForPriorCall(paymentStateContext, result); + return result; } protected void executePluginOnSuccessCalls(final List paymentControlPluginNames, final PaymentControlContext paymentControlContext) { - Iterable inputPluginProperties = paymentStateContext.getProperties(); - for (final String pluginName : paymentControlPluginNames) { - final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName); - if (plugin != null) { - try { - final OnSuccessPaymentControlResult result = plugin.onSuccessCall(paymentControlContext, inputPluginProperties); - if (result.getAdjustedPluginProperties() != null) { - inputPluginProperties = result.getAdjustedPluginProperties(); - } - // Exceptions from the control plugins are ignored (and logged) because the semantics on what to do are undefined. - } catch (final PaymentControlApiException e) { - logger.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + paymentControlContext.getPaymentExternalKey(), e); - } catch (final RuntimeException e) { - logger.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + paymentControlContext.getPaymentExternalKey(), e); - } - } - } - adjustStateContextPluginProperties(paymentStateContext, inputPluginProperties); + final OnSuccessPaymentControlResult result = controlPluginRunner.executePluginOnSuccessCalls(paymentStateContext.getAccount(), + paymentControlContext.getPaymentMethodId(), + paymentStateControlContext.getAttemptId(), + paymentStateContext.getPaymentId(), + paymentStateContext.getPaymentExternalKey(), + paymentStateContext.getPaymentTransactionExternalKey(), + PaymentApiType.PAYMENT_TRANSACTION, + paymentStateContext.getTransactionType(), + null, + paymentControlContext.getAmount(), + paymentControlContext.getCurrency(), + paymentStateControlContext.isApiPayment(), + paymentControlPluginNames, + paymentStateContext.getProperties(), + paymentStateContext.getCallContext()); + adjustStateContextPluginProperties(paymentStateContext, result.getAdjustedPluginProperties()); } private OperationResult executePluginOnFailureCallsAndSetRetryDate(final PaymentStateControlContext paymentStateControlContext, final PaymentControlContext paymentControlContext) { @@ -250,32 +229,23 @@ private OperationResult executePluginOnFailureCallsAndSetRetryDate(final Payment private DateTime executePluginOnFailureCalls(final List paymentControlPluginNames, final PaymentControlContext paymentControlContext) { - DateTime candidate = null; - Iterable inputPluginProperties = paymentStateContext.getProperties(); - - for (final String pluginName : paymentControlPluginNames) { - final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName); - if (plugin != null) { - try { - final OnFailurePaymentControlResult result = plugin.onFailureCall(paymentControlContext, inputPluginProperties); - if (candidate == null) { - candidate = result.getNextRetryDate(); - } else if (result.getNextRetryDate() != null) { - candidate = candidate.compareTo(result.getNextRetryDate()) > 0 ? result.getNextRetryDate() : candidate; - } - - if (result.getAdjustedPluginProperties() != null) { - inputPluginProperties = result.getAdjustedPluginProperties(); - } - - } catch (final PaymentControlApiException e) { - logger.warn("Plugin " + pluginName + " failed to return next retryDate for payment " + paymentControlContext.getPaymentExternalKey(), e); - return candidate; - } - } - } - adjustStateContextPluginProperties(paymentStateContext, inputPluginProperties); - return candidate; + final OnFailurePaymentControlResult result = controlPluginRunner.executePluginOnFailureCalls(paymentStateContext.getAccount(), + paymentControlContext.getPaymentMethodId(), + paymentStateControlContext.getAttemptId(), + paymentStateContext.getPaymentId(), + paymentStateContext.getPaymentExternalKey(), + paymentStateContext.getPaymentTransactionExternalKey(), + PaymentApiType.PAYMENT_TRANSACTION, + paymentStateContext.getTransactionType(), + null, + paymentControlContext.getAmount(), + paymentControlContext.getCurrency(), + paymentStateControlContext.isApiPayment(), + paymentControlPluginNames, + paymentStateContext.getProperties(), + paymentStateContext.getCallContext()); + adjustStateContextPluginProperties(paymentStateContext, result.getAdjustedPluginProperties()); + return result.getNextRetryDate(); } private void adjustStateContextForPriorCall(final PaymentStateContext inputContext, @Nullable final PriorPaymentControlResult pluginResult) { @@ -303,127 +273,4 @@ private void adjustStateContextPluginProperties(final PaymentStateContext inputC final PaymentStateControlContext input = (PaymentStateControlContext) inputContext; input.setProperties(pluginProperties); } - - public static class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext { - - private final Account account; - private final UUID paymentMethodId; - private final UUID attemptId; - private final UUID paymentId; - private final String paymentExternalKey; - private final UUID transactionId; - private final String transactionExternalKey; - private final TransactionType transactionType; - private final BigDecimal amount; - private final Currency currency; - private final BigDecimal processedAmount; - private final Currency processedCurrency; - private final boolean isApiPayment; - - public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey, final TransactionType transactionType, final BigDecimal amount, final Currency currency, - final boolean isApiPayment, final CallContext callContext) { - this(account, paymentMethodId, attemptId, paymentId, paymentExternalKey, null, transactionExternalKey, transactionType, amount, currency, null, null, isApiPayment, callContext); - } - - public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType, - final BigDecimal amount, final Currency currency, @Nullable final BigDecimal processedAmount, @Nullable final Currency processedCurrency, final boolean isApiPayment, final CallContext callContext) { - super(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate()); - this.account = account; - this.paymentMethodId = paymentMethodId; - this.attemptId = attemptId; - this.paymentId = paymentId; - this.paymentExternalKey = paymentExternalKey; - this.transactionId = transactionId; - this.transactionExternalKey = transactionExternalKey; - this.transactionType = transactionType; - this.amount = amount; - this.currency = currency; - this.processedAmount = processedAmount; - this.processedCurrency = processedCurrency; - this.isApiPayment = isApiPayment; - } - - @Override - public UUID getAccountId() { - return account.getId(); - } - - @Override - public String getPaymentExternalKey() { - return paymentExternalKey; - } - - @Override - public String getTransactionExternalKey() { - return transactionExternalKey; - } - - @Override - public TransactionType getTransactionType() { - return transactionType; - } - - @Override - public BigDecimal getAmount() { - return amount; - } - - @Override - public Currency getCurrency() { - return currency; - } - - @Override - public UUID getPaymentMethodId() { - return paymentMethodId; - } - - @Override - public UUID getPaymentId() { - return paymentId; - } - - @Override - public UUID getAttemptPaymentId() { - return attemptId; - } - - @Override - public BigDecimal getProcessedAmount() { - return processedAmount; - } - - @Override - public Currency getProcessedCurrency() { - return processedCurrency; - } - - @Override - public boolean isApiPayment() { - return isApiPayment; - } - - public UUID getTransactionId() { - return transactionId; - } - - @Override - public String toString() { - return "DefaultPaymentControlContext{" + - "account=" + account + - ", paymentMethodId=" + paymentMethodId + - ", attemptId=" + attemptId + - ", paymentId=" + paymentId + - ", paymentExternalKey='" + paymentExternalKey + '\'' + - ", transactionId=" + transactionId + - ", transactionExternalKey='" + transactionExternalKey + '\'' + - ", transactionType=" + transactionType + - ", amount=" + amount + - ", currency=" + currency + - ", processedAmount=" + processedAmount + - ", processedCurrency=" + processedCurrency + - ", isApiPayment=" + isApiPayment + - '}'; - } - } } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java index 602231e823..7ad848fa1d 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java @@ -27,8 +27,8 @@ public class PurchaseControlOperation extends OperationControlCallback { - public PurchaseControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration retryPluginRegistry) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry); + public PurchaseControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java index d9152f09dd..5f4d7cde26 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java @@ -27,8 +27,8 @@ public class RefundControlOperation extends OperationControlCallback { - public RefundControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration paymentControlPluginRegistry) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + public RefundControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java index 67f4d9b8a4..00200d1c11 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java @@ -27,8 +27,8 @@ public class VoidControlOperation extends OperationControlCallback { - public VoidControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration paymentControlPluginRegistry) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry); + public VoidControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java index fb3c06bf6c..0c0b0a76c5 100644 --- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java +++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java @@ -47,6 +47,7 @@ import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper; import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper; import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner; +import org.killbill.billing.payment.core.sm.control.ControlPluginRunner; import org.killbill.billing.payment.dao.DefaultPaymentDao; import org.killbill.billing.payment.dao.PaymentDao; import org.killbill.billing.payment.invoice.PaymentTagHandler; @@ -125,6 +126,8 @@ protected void installStateMachines() { bind(StateMachineProvider.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toInstance(new StateMachineProvider(DEFAULT_STATE_MACHINE_PAYMENT_XML)); bind(StateMachineConfig.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toProvider(Key.get(StateMachineProvider.class, Names.named(STATE_MACHINE_PAYMENT))); bind(PaymentStateMachineHelper.class).asEagerSingleton(); + + bind(ControlPluginRunner.class).asEagerSingleton(); } protected void installAutomatonRunner() { diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java index 91408b7ae3..d1bb59dac5 100644 --- a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java +++ b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java @@ -22,10 +22,20 @@ public class DefaultFailureCallResult implements OnFailurePaymentControlResult { + private final Iterable adjustedPluginProperties; private final DateTime nextRetryDate; + public DefaultFailureCallResult() { + this(null, null); + } + public DefaultFailureCallResult(final DateTime nextRetryDate) { + this(nextRetryDate, null); + } + + public DefaultFailureCallResult(final DateTime nextRetryDate, final Iterable adjustedPluginProperties) { this.nextRetryDate = nextRetryDate; + this.adjustedPluginProperties = adjustedPluginProperties; } @Override @@ -35,6 +45,6 @@ public DateTime getNextRetryDate() { @Override public Iterable getAdjustedPluginProperties() { - return null; + return adjustedPluginProperties; } } diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java index 006003688f..a97dc584e2 100644 --- a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java +++ b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java @@ -22,8 +22,18 @@ public class DefaultOnSuccessPaymentControlResult implements OnSuccessPaymentControlResult { + private final Iterable adjustedPluginProperties; + + public DefaultOnSuccessPaymentControlResult() { + this(null); + } + + public DefaultOnSuccessPaymentControlResult(final Iterable adjustedPluginProperties) { + this.adjustedPluginProperties = adjustedPluginProperties; + } + @Override public Iterable getAdjustedPluginProperties() { - return null; + return adjustedPluginProperties; } } diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java new file mode 100644 index 0000000000..a53830f6c6 --- /dev/null +++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java @@ -0,0 +1,4 @@ +package org.killbill.billing.payment.api; + +public class TestPaymentGatewayApi { +} diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java index f7007c01d2..3dd00a4caf 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java @@ -28,6 +28,7 @@ import org.killbill.billing.payment.api.TransactionStatus; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.sm.control.AuthorizeControlOperation; +import org.killbill.billing.payment.core.sm.control.ControlPluginRunner; import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext; import org.killbill.billing.payment.dao.PaymentDao; import org.killbill.billing.payment.dao.PaymentModelDao; @@ -45,8 +46,8 @@ public class MockRetryAuthorizeOperationCallback extends AuthorizeControlOperati private Exception exception; private OperationResult result; - public MockRetryAuthorizeOperationCallback(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration retryPluginRegistry, final PaymentDao paymentDao, final Clock clock) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry); + public MockRetryAuthorizeOperationCallback(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner, final PaymentDao paymentDao, final Clock clock) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); this.paymentDao = paymentDao; this.clock = clock; } diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java index 7a69891ff0..14f6758796 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java @@ -37,6 +37,7 @@ import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionType; import org.killbill.billing.payment.core.PaymentProcessor; +import org.killbill.billing.payment.core.sm.control.ControlPluginRunner; import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext; import org.killbill.billing.payment.dao.PaymentDao; import org.killbill.billing.payment.dispatcher.PluginDispatcher; @@ -62,8 +63,8 @@ public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAut @Inject public MockRetryablePaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, @Named(PaymentModule.STATE_MACHINE_RETRY) final StateMachineConfig retryStateMachine, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration pluginRegistry, final OSGIServiceRegistration retryPluginRegistry, final Clock clock, final TagInternalApi tagApi, final PaymentProcessor paymentProcessor, @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, - final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper retrySMHelper, final PersistentBus eventBus) { - super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, eventBus); + final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper retrySMHelper, final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) { + super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, controlPluginRunner, eventBus); } @Override diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java index b016e7a4a1..433806e98d 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java @@ -41,6 +41,7 @@ import org.killbill.billing.payment.api.TransactionType; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.PluginControlPaymentProcessor; +import org.killbill.billing.payment.core.sm.control.ControlPluginRunner; import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext; import org.killbill.billing.payment.dao.MockPaymentDao; import org.killbill.billing.payment.dao.PaymentAttemptModelDao; @@ -109,6 +110,8 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB { @Inject private PaymentControlStateMachineHelper retrySMHelper; @Inject + private ControlPluginRunner controlPluginRunner; + @Inject private InternalCallContextFactory internalCallContextFactory; private Account account; @@ -169,6 +172,7 @@ public void beforeMethod() throws Exception { executor, paymentSMHelper, retrySMHelper, + controlPluginRunner, eventBus); paymentStateContext = @@ -191,7 +195,7 @@ public void beforeMethod() throws Exception { runner.getPaymentPluginDispatcher(), paymentStateContext, null, - runner.getRetryPluginRegistry(), + controlPluginRunner, paymentDao, clock); From 9d24b52f92622106ad68dacd63eaf3d132d484f4 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 12 Aug 2015 17:20:36 -0700 Subject: [PATCH 015/137] Add payment tests for new api buildFormDescriptorWithPaymentControl. Fixes #329 --- .../payment/api/DefaultPaymentGatewayApi.java | 4 +- .../billing/payment/PaymentTestSuiteNoDB.java | 3 + .../PaymentTestSuiteWithEmbeddedDB.java | 3 + .../api/TestPaymentApiWithControl.java | 4 +- .../payment/api/TestPaymentGatewayApi.java | 4 - ...stPaymentGatewayApiWithPaymentControl.java | 257 ++++++++++++++++++ 6 files changed, 267 insertions(+), 8 deletions(-) delete mode 100644 payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java create mode 100644 payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java index ae10edb4b6..b4eb5c2fdd 100644 --- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java @@ -55,7 +55,7 @@ public DefaultPaymentGatewayApi(final PaymentConfig paymentConfig, } @Override - public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account, @Nullable final UUID paymentMethodId, final Iterable customFields, final Iterable properties, final CallContext callContext) throws PaymentApiException { + public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account, final UUID paymentMethodId, final Iterable customFields, final Iterable properties, final CallContext callContext) throws PaymentApiException { final UUID paymentMethodIdToUse = paymentMethodId != null ? paymentMethodId : account.getPaymentMethodId(); if (paymentMethodId == null) { @@ -66,7 +66,7 @@ public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account } @Override - public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, final Iterable customFields, final Iterable properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException { + public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, final UUID paymentMethodId, final Iterable customFields, final Iterable properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException { return executeWithPaymentControl(account, paymentMethodId, properties, paymentOptions, callContext, new WithPaymentControlCallback() { @Override diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java index 778f2797d8..9c19560c6c 100644 --- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java +++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java @@ -23,6 +23,7 @@ import org.killbill.billing.invoice.api.InvoiceInternalApi; import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.PaymentApi; +import org.killbill.billing.payment.api.PaymentGatewayApi; import org.killbill.billing.payment.core.PaymentMethodProcessor; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.PluginControlPaymentProcessor; @@ -62,6 +63,8 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB { @Inject protected PaymentApi paymentApi; @Inject + protected PaymentGatewayApi paymentGatewayApi; + @Inject protected AccountInternalApi accountInternalApi; @Inject protected TestPaymentHelper testHelper; diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java index 81c48135ec..1ab3f64aa5 100644 --- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java +++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java @@ -23,6 +23,7 @@ import org.killbill.billing.invoice.api.InvoiceInternalApi; import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.PaymentApi; +import org.killbill.billing.payment.api.PaymentGatewayApi; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.PaymentMethodProcessor; import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper; @@ -60,6 +61,8 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu @Inject protected PaymentApi paymentApi; @Inject + protected PaymentGatewayApi paymentGatewayApi; + @Inject protected AccountInternalApi accountApi; @Inject protected PaymentStateMachineHelper paymentSMHelper; diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java index 5d528a11c3..34f146db8f 100644 --- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java +++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java @@ -46,7 +46,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB { @Inject - private OSGIServiceRegistration retryPluginRegistry; + private OSGIServiceRegistration controlPluginRegistry; private Account account; private UUID newPaymentMethodId; @@ -58,7 +58,7 @@ public void beforeMethod() throws Exception { final PaymentMethodPlugin paymentMethodInfo = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, null); newPaymentMethodId = paymentApi.addPaymentMethod(account, paymentMethodInfo.getExternalPaymentMethodId(), MockPaymentProviderPlugin.PLUGIN_NAME, false, paymentMethodInfo, ImmutableList.of(), callContext); - retryPluginRegistry.registerService(new OSGIServiceDescriptor() { + controlPluginRegistry.registerService(new OSGIServiceDescriptor() { @Override public String getPluginSymbolicName() { return null; diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java deleted file mode 100644 index a53830f6c6..0000000000 --- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.killbill.billing.payment.api; - -public class TestPaymentGatewayApi { -} diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java new file mode 100644 index 0000000000..fb29bff014 --- /dev/null +++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java @@ -0,0 +1,257 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.payment.api; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.killbill.billing.account.api.Account; +import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult; +import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult; +import org.killbill.billing.control.plugin.api.PaymentControlApiException; +import org.killbill.billing.control.plugin.api.PaymentControlContext; +import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.control.plugin.api.PriorPaymentControlResult; +import org.killbill.billing.osgi.api.OSGIServiceDescriptor; +import org.killbill.billing.osgi.api.OSGIServiceRegistration; +import org.killbill.billing.payment.PaymentTestSuiteNoDB; +import org.killbill.billing.payment.retry.DefaultFailureCallResult; +import org.killbill.billing.payment.retry.DefaultOnSuccessPaymentControlResult; +import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +public class TestPaymentGatewayApiWithPaymentControl extends PaymentTestSuiteNoDB { + + @Inject + private OSGIServiceRegistration controlPluginRegistry; + + private Account account; + + private PaymentOptions paymentOptions; + + private TestPaymentGatewayApiControlPlugin plugin; + private TestPaymentGatewayApiValidationPlugin validationPlugin; + + @BeforeMethod(groups = "fast") + public void beforeMethod() throws Exception { + + super.beforeMethod(); + + account = testHelper.createTestAccount("arthur@gmail.com", true); + + paymentOptions = new PaymentOptions() { + @Override + public boolean isExternalPayment() { + return false; + } + + @Override + public List getPaymentControlPluginNames() { + return ImmutableList.of(TestPaymentGatewayApiControlPlugin.PLUGIN_NAME, TestPaymentGatewayApiValidationPlugin.VALIDATION_PLUGIN_NAME); + } + }; + + plugin = new TestPaymentGatewayApiControlPlugin(); + + controlPluginRegistry.registerService(new OSGIServiceDescriptor() { + @Override + public String getPluginSymbolicName() { + return null; + } + + @Override + public String getRegistrationName() { + return TestPaymentGatewayApiControlPlugin.PLUGIN_NAME; + } + }, plugin); + + validationPlugin = new TestPaymentGatewayApiValidationPlugin(); + controlPluginRegistry.registerService(new OSGIServiceDescriptor() { + @Override + public String getPluginSymbolicName() { + return null; + } + + @Override + public String getRegistrationName() { + return TestPaymentGatewayApiValidationPlugin.VALIDATION_PLUGIN_NAME; + } + }, validationPlugin); + + } + + @Test(groups = "fast") + public void testBuildFormDescriptorWithPaymentControl() throws PaymentApiException { + + final List initialProperties = new ArrayList(); + initialProperties.add(new PluginProperty("keyA", "valueA", true)); + initialProperties.add(new PluginProperty("keyB", "valueB", true)); + initialProperties.add(new PluginProperty("keyC", "valueC", true)); + + final List priorNewProperties = new ArrayList(); + priorNewProperties.add(new PluginProperty("keyD", "valueD", true)); + final List priorRemovedProperties = new ArrayList(); + priorRemovedProperties.add(new PluginProperty("keyA", "valueA", true)); + plugin.setPriorCallProperties(priorNewProperties, priorRemovedProperties); + + final List onResultNewProperties = new ArrayList(); + onResultNewProperties.add(new PluginProperty("keyE", "valueE", true)); + final List onResultRemovedProperties = new ArrayList(); + onResultRemovedProperties.add(new PluginProperty("keyB", "valueB", true)); + plugin.setOnResultProperties(onResultNewProperties, onResultRemovedProperties); + + final List expectedPriorCallProperties = new ArrayList(); + expectedPriorCallProperties.add(new PluginProperty("keyB", "valueB", true)); + expectedPriorCallProperties.add(new PluginProperty("keyC", "valueC", true)); + expectedPriorCallProperties.add(new PluginProperty("keyD", "valueD", true)); + + validationPlugin.setExpectedPriorCallProperties(expectedPriorCallProperties); + + final List expectedProperties = new ArrayList(); + expectedProperties.add(new PluginProperty("keyC", "valueC", true)); + expectedProperties.add(new PluginProperty("keyD", "valueD", true)); + expectedProperties.add(new PluginProperty("keyE", "valueE", true)); + + validationPlugin.setExpectedProperties(expectedProperties); + + paymentGatewayApi.buildFormDescriptorWithPaymentControl(account, account.getPaymentMethodId(), ImmutableList.of(), initialProperties, paymentOptions, callContext); + + } + + public static class TestPaymentGatewayApiValidationPlugin implements PaymentControlPluginApi { + + public static final String VALIDATION_PLUGIN_NAME = "TestPaymentGatewayApiValidationPlugin"; + + private Iterable expectedPriorCallProperties; + private Iterable expectedProperties; + + public TestPaymentGatewayApiValidationPlugin() { + this.expectedPriorCallProperties = ImmutableList.of(); + this.expectedProperties = ImmutableList.of(); + } + + public void setExpectedProperties(final Iterable expectedProperties) { + this.expectedProperties = expectedProperties; + } + + public void setExpectedPriorCallProperties(final List expectedPriorCallProperties) { + this.expectedPriorCallProperties = expectedPriorCallProperties; + } + + @Override + public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable properties) throws PaymentControlApiException { + validate(properties, expectedPriorCallProperties); + return new DefaultPriorPaymentControlResult(false); + } + + @Override + public OnSuccessPaymentControlResult onSuccessCall(final PaymentControlContext paymentControlContext, final Iterable properties) throws PaymentControlApiException { + validate(properties, expectedProperties); + return new DefaultOnSuccessPaymentControlResult(); + } + + @Override + public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable properties) throws PaymentControlApiException { + validate(properties, expectedProperties); + return new DefaultFailureCallResult(); + } + + private static void validate(final Iterable properties, final Iterable expected) { + Assert.assertEquals(Iterables.size(properties), Iterables.size(expected), "Got " + Iterables.size(properties) + "properties" + ", expected " + Iterables.size(expected)); + + for (final PluginProperty curExpected : expected) { + Assert.assertTrue(Iterables.any(properties, new Predicate() { + @Override + public boolean apply(final PluginProperty input) { + return input.getKey().equals(curExpected.getKey()) && input.getValue().equals(curExpected.getValue()); + + } + }), "Cannot find expected property" + curExpected.getKey()); + } + } + + } + + public static class TestPaymentGatewayApiControlPlugin implements PaymentControlPluginApi { + + public static final String PLUGIN_NAME = "TestPaymentGatewayApiControlPlugin"; + + private Iterable newPriorCallProperties; + private Iterable removedPriorCallProperties; + + private Iterable newOnResultProperties; + private Iterable removedOnResultProperties; + + public TestPaymentGatewayApiControlPlugin() { + this.newPriorCallProperties = ImmutableList.of(); + this.removedPriorCallProperties = ImmutableList.of(); + this.newOnResultProperties = ImmutableList.of(); + this.removedOnResultProperties = ImmutableList.of(); + } + + public void setPriorCallProperties(final Iterable newPriorCallProperties, final Iterable removedPriorCallProperties) { + this.newPriorCallProperties = newPriorCallProperties; + this.removedPriorCallProperties = removedPriorCallProperties; + } + + public void setOnResultProperties(final Iterable newOnResultProperties, final Iterable removedOnResultProperties) { + this.newOnResultProperties = newOnResultProperties; + this.removedOnResultProperties = removedOnResultProperties; + } + + @Override + public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable properties) throws PaymentControlApiException { + return new DefaultPriorPaymentControlResult(false, null, null, null, getAdjustedProperties(properties, newPriorCallProperties, removedPriorCallProperties)); + } + + @Override + public OnSuccessPaymentControlResult onSuccessCall(final PaymentControlContext paymentControlContext, final Iterable properties) throws PaymentControlApiException { + return new DefaultOnSuccessPaymentControlResult(getAdjustedProperties(properties, newOnResultProperties, removedOnResultProperties)); + } + + @Override + public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable properties) throws PaymentControlApiException { + return new DefaultFailureCallResult(null, getAdjustedProperties(properties, newOnResultProperties, removedOnResultProperties)); + } + + private static Iterable getAdjustedProperties(final Iterable input, final Iterable newProperties, final Iterable removedProperties) { + final Iterable filtered = Iterables.filter(input, new Predicate() { + @Override + public boolean apply(final PluginProperty p) { + final boolean toBeRemoved = Iterables.any(removedProperties, new Predicate() { + @Override + public boolean apply(final PluginProperty a) { + return a.getKey().equals(p.getKey()) && a.getValue().equals(p.getValue()); + } + }); + return !toBeRemoved; + } + }); + return Iterables.concat(filtered, newProperties); + } + } + +} From 7c6bf27f311efc3fb17cb454ef108838a4e0c81a Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 12 Aug 2015 17:23:07 -0700 Subject: [PATCH 016/137] Fixes #350 --- .../org/killbill/billing/payment/api/DefaultPaymentApi.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java index 656f830c03..3da498d432 100644 --- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java @@ -166,7 +166,7 @@ public Payment createPurchase(final Account account, final UUID paymentMethodId, } @Override - public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String paymentTransactionExternalKey, + public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey, final Iterable properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException { final List paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions); if (paymentControlPluginNames.isEmpty()) { @@ -179,7 +179,6 @@ public Payment createPurchaseWithPaymentControl(final Account account, @Nullable checkPositiveAmount(amount); checkNotNullParameter(currency, "currency"); } - checkNotNullParameter(paymentExternalKey, "paymentExternalKey"); checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey"); checkNotNullParameter(properties, "plugin properties"); From c0770967de3e9ee5b6b2668f8980ca863a29d493 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 12 Aug 2015 19:01:26 -0700 Subject: [PATCH 017/137] Fix regression introduced by 69aee775b22fcc9b61c7f472a4d8f6394e23f7bc --- .../payment/api/DefaultPaymentGatewayApi.java | 4 +- .../core/sm/control/ControlPluginRunner.java | 43 ++++++++++--------- .../sm/control/OperationControlCallback.java | 12 ++++-- .../InvoicePaymentControlPluginApi.java | 3 ++ 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java index b4eb5c2fdd..39117a5bee 100644 --- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java @@ -124,9 +124,9 @@ private T executeWithPaymentControl(@Nullable final Account account, final T result = callback.doPaymentGatewayApiOperation(priorCallResult.getAdjustedPluginProperties()); controlPluginRunner.executePluginOnSuccessCalls(account, paymentMethodId, - null, null, null, null, + null, null, null, null, null, PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR, - null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext); + null, null, null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext); return result; } catch (final PaymentApiException e) { controlPluginRunner.executePluginOnFailureCalls(account, diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java index da94cfdf33..41c37a2f0c 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java @@ -68,7 +68,7 @@ public PriorPaymentControlResult executePluginPriorCalls(final Account account, final HPPType hppType, final BigDecimal amount, final Currency currency, - final boolean isApiPayment, + final boolean isApiPayment, final List paymentControlPluginNames, final Iterable pluginProperties, final CallContext callContext) throws PaymentControlApiException { @@ -124,34 +124,39 @@ public PriorPaymentControlResult executePluginPriorCalls(final Account account, return prevResult; } - public OnSuccessPaymentControlResult executePluginOnSuccessCalls(final Account account, - final UUID paymentMethodId, - final UUID paymentAttemptId, - final UUID paymentId, - final String paymentExternalKey, - final String paymentTransactionExternalKey, - final PaymentApiType paymentApiType, - final TransactionType transactionType, - final HPPType hppType, - final BigDecimal amount, - final Currency currency, - final boolean isApiPayment, - final List paymentControlPluginNames, - final Iterable pluginProperties, - final CallContext callContext) { + final UUID paymentMethodId, + final UUID paymentAttemptId, + final UUID paymentId, + final String paymentExternalKey, + final UUID transactionId, + final String paymentTransactionExternalKey, + final PaymentApiType paymentApiType, + final TransactionType transactionType, + final HPPType hppType, + final BigDecimal amount, + final Currency currency, + final BigDecimal processedAmount, + final Currency processedCurrency, + final boolean isApiPayment, + final List paymentControlPluginNames, + final Iterable pluginProperties, + final CallContext callContext) { final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account, paymentMethodId, paymentAttemptId, paymentId, paymentExternalKey, + transactionId, paymentTransactionExternalKey, paymentApiType, transactionType, hppType, amount, currency, + processedAmount, + processedCurrency, isApiPayment, callContext); Iterable inputPluginProperties = pluginProperties; @@ -174,7 +179,6 @@ public OnSuccessPaymentControlResult executePluginOnSuccessCalls(final Account a return new DefaultOnSuccessPaymentControlResult(inputPluginProperties); } - public OnFailurePaymentControlResult executePluginOnFailureCalls(final Account account, final UUID paymentMethodId, final UUID paymentAttemptId, @@ -186,7 +190,7 @@ public OnFailurePaymentControlResult executePluginOnFailureCalls(final Account a final HPPType hppType, final BigDecimal amount, final Currency currency, - final boolean isApiPayment, + final boolean isApiPayment, final List paymentControlPluginNames, final Iterable pluginProperties, final CallContext callContext) { @@ -232,9 +236,6 @@ public OnFailurePaymentControlResult executePluginOnFailureCalls(final Account a return new DefaultFailureCallResult(candidate, inputPluginProperties); } - - - public static class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext { private final Account account; diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java index f2458e8b3c..a1e89282fe 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java @@ -200,18 +200,22 @@ private PriorPaymentControlResult executePluginPriorCalls(final List pay } protected void executePluginOnSuccessCalls(final List paymentControlPluginNames, final PaymentControlContext paymentControlContext) { - + // Values that were obtained/chnaged after the payment call was made (paymentId, processedAmount, processedCurrency,... needs to be extracted from the paymentControlContext) + // paymentId, paymentExternalKey, transactionAmount, transaction currency are extracted from paymentControlContext which was update from the operation result. final OnSuccessPaymentControlResult result = controlPluginRunner.executePluginOnSuccessCalls(paymentStateContext.getAccount(), - paymentControlContext.getPaymentMethodId(), + paymentStateContext.getPaymentMethodId(), paymentStateControlContext.getAttemptId(), - paymentStateContext.getPaymentId(), - paymentStateContext.getPaymentExternalKey(), + paymentControlContext.getPaymentId(), + paymentControlContext.getPaymentExternalKey(), + paymentControlContext.getTransactionId(), paymentStateContext.getPaymentTransactionExternalKey(), PaymentApiType.PAYMENT_TRANSACTION, paymentStateContext.getTransactionType(), null, paymentControlContext.getAmount(), paymentControlContext.getCurrency(), + paymentControlContext.getProcessedAmount(), + paymentControlContext.getProcessedCurrency(), paymentStateControlContext.isApiPayment(), paymentControlPluginNames, paymentStateContext.getProperties(), diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java index 0484897d87..851030134f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java @@ -33,6 +33,7 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; +import org.killbill.billing.control.plugin.api.PaymentApiType; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceInternalApi; @@ -114,7 +115,9 @@ public InvoicePaymentControlPluginApi(final PaymentConfig paymentConfig, final I @Override public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable pluginProperties) throws PaymentControlApiException { + final TransactionType transactionType = paymentControlContext.getTransactionType(); + Preconditions.checkArgument(paymentControlContext.getPaymentApiType() == PaymentApiType.PAYMENT_TRANSACTION); Preconditions.checkArgument(transactionType == TransactionType.PURCHASE || transactionType == TransactionType.REFUND || transactionType == TransactionType.CHARGEBACK); From 601bf5380ab71ac13c046210e4ddc862481ec62a Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Mon, 17 Aug 2015 18:03:19 -0700 Subject: [PATCH 018/137] Update server properties to match new queue configuration change --- pom.xml | 2 +- .../main/resources/killbill-server.properties | 12 +- .../org/killbill/billing/jaxrs/TestRL.java | 321 ++++++++++++++++++ 3 files changed, 326 insertions(+), 9 deletions(-) create mode 100644 profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java diff --git a/pom.xml b/pom.xml index d61de93991..935cc94654 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.33-SNAPSHOT + 0.36-SNAPSHOT killbill 0.15.3-SNAPSHOT diff --git a/profiles/killbill/src/main/resources/killbill-server.properties b/profiles/killbill/src/main/resources/killbill-server.properties index e418e118f4..c4c568ec60 100644 --- a/profiles/killbill/src/main/resources/killbill-server.properties +++ b/profiles/killbill/src/main/resources/killbill-server.properties @@ -31,25 +31,21 @@ org.killbill.catalog.uri=SpyCarAdvanced.xml # NotificationQ, Bus, ExtBus config org.killbill.notificationq.main.sleep=1000 org.killbill.notificationq.main.claimed=10 -org.killbill.notificationq.main.sticky=true +org.killbill.notificationq.main.queue.mode=STICKY_POLLING -org.killbill.persistent.bus.external.sticky=true +org.killbill.persistent.bus.external.queue.mode=STICKY_EVENTS org.killbill.persistent.bus.external.inMemory=true -#org.killbill.persistent.bus.external.sticky=true +#org.killbill.persistent.bus.external.queue.mode=STICKY_EVENTS #org.killbill.persistent.bus.external.claimed=1 -#org.killbill.persistent.bus.external.inflight.claimed=1 #org.killbill.persistent.bus.external.nbThreads=1 #org.killbill.persistent.bus.external.sleep=0 -#org.killbill.persistent.bus.external.useInflightQ=true #org.killbill.persistent.bus.external.queue.capacity=100 -org.killbill.persistent.bus.main.sticky=true +org.killbill.persistent.bus.main.queue.mode=STICKY_EVENTS org.killbill.persistent.bus.main.claimed=1 -org.killbill.persistent.bus.main.inflight.claimed=1 org.killbill.persistent.bus.main.nbThreads=1 org.killbill.persistent.bus.main.sleep=0 -org.killbill.persistent.bus.main.useInflightQ=true org.killbill.persistent.bus.main.queue.capacity=100 # Start KB in multi-tenant diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java new file mode 100644 index 0000000000..11e19dec0b --- /dev/null +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java @@ -0,0 +1,321 @@ +/* + * Copyright 2010-2013 Ning, Inc. + * Copyright 2014 Groupon, Inc + * Copyright 2014 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.killbill.billing.ErrorCode; +import org.killbill.billing.ObjectType; +import org.killbill.billing.client.KillBillClientException; +import org.killbill.billing.client.model.Account; +import org.killbill.billing.client.model.Accounts; +import org.killbill.billing.client.model.AuditLog; +import org.killbill.billing.client.model.CustomField; +import org.killbill.billing.client.model.InvoicePayments; +import org.killbill.billing.client.model.PaymentMethod; +import org.killbill.billing.client.model.PaymentMethodPluginDetail; +import org.killbill.billing.client.model.Tag; +import org.killbill.billing.util.api.AuditLevel; +import org.testng.Assert; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +public class TestRL extends TestJaxrsBase { + + @Test(groups = "slow", description = "Verify no PII data is required") + public void testEmptyAccount() throws Exception { + final Account emptyAccount = new Account(); + + final Account account = killBillClient.createAccount(emptyAccount, createdBy, reason, comment); + Assert.assertNotNull(account.getExternalKey()); + Assert.assertNull(account.getName()); + Assert.assertNull(account.getEmail()); + } + + @Test(groups = "slow", description = "Verify external key is unique") + public void testUniqueExternalKey() throws Exception { + // Verify the external key is not mandatory + final Account inputWithNoExternalKey = getAccount(UUID.randomUUID().toString(), null, UUID.randomUUID().toString()); + Assert.assertNull(inputWithNoExternalKey.getExternalKey()); + + final Account account = killBillClient.createAccount(inputWithNoExternalKey, createdBy, reason, comment); + Assert.assertNotNull(account.getExternalKey()); + + final Account inputWithSameExternalKey = getAccount(UUID.randomUUID().toString(), account.getExternalKey(), UUID.randomUUID().toString()); + try { + killBillClient.createAccount(inputWithSameExternalKey, createdBy, reason, comment); + Assert.fail(); + } catch (final KillBillClientException e) { + Assert.assertEquals(e.getBillingException().getCode(), (Integer) ErrorCode.ACCOUNT_ALREADY_EXISTS.getCode()); + } + } + + @Test(groups = "slow", description = "Can create, retrieve, search and update accounts") + public void testAccountOk() throws Exception { + final Account input = createAccount(); + + // Retrieves by external key + final Account retrievedAccount = killBillClient.getAccount(input.getExternalKey()); + Assert.assertTrue(retrievedAccount.equals(input)); + + // Try search endpoint + searchAccount(input, retrievedAccount); + + // Update Account + final Account newInput = new Account(input.getAccountId(), + "zozo", 4, input.getExternalKey(), "rr@google.com", 18, + "USD", null, "UTC", "bl1", "bh2", "", "", "ca", "San Francisco", "usa", "en", "415-255-2991", + false, false, null, null); + final Account updatedAccount = killBillClient.updateAccount(newInput, createdBy, reason, comment); + Assert.assertTrue(updatedAccount.equals(newInput)); + + // Try search endpoint + searchAccount(input, null); + } + + @Test(groups = "slow", description = "Can retrieve the account balance") + public void testAccountWithBalance() throws Exception { + final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice(); + + final Account accountWithBalance = killBillClient.getAccount(accountJson.getAccountId(), true, false); + final BigDecimal accountBalance = accountWithBalance.getAccountBalance(); + Assert.assertTrue(accountBalance.compareTo(BigDecimal.ZERO) > 0); + } + + @Test(groups = "slow", description = "Cannot update a non-existent account") + public void testUpdateNonExistentAccount() throws Exception { + final Account input = getAccount(); + + Assert.assertNull(killBillClient.updateAccount(input, createdBy, reason, comment)); + } + + @Test(groups = "slow", description = "Cannot retrieve non-existent account") + public void testAccountNonExistent() throws Exception { + Assert.assertNull(killBillClient.getAccount(UUID.randomUUID())); + Assert.assertNull(killBillClient.getAccount(UUID.randomUUID().toString())); + } + + @Test(groups = "slow", description = "Can CRUD payment methods") + public void testAccountPaymentMethods() throws Exception { + final Account accountJson = createAccount(); + assertNotNull(accountJson); + + final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail(); + info.setProperties(getPaymentMethodCCProperties()); + PaymentMethod paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), true, PLUGIN_NAME, info); + final PaymentMethod paymentMethodCC = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment); + assertTrue(paymentMethodCC.getIsDefault()); + + // + // Add another payment method + // + final PaymentMethodPluginDetail info2 = new PaymentMethodPluginDetail(); + info2.setProperties(getPaymentMethodPaypalProperties()); + paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), false, PLUGIN_NAME, info2); + final PaymentMethod paymentMethodPP = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment); + assertFalse(paymentMethodPP.getIsDefault()); + + // + // FETCH ALL PAYMENT METHODS + // + List paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId()); + assertEquals(paymentMethods.size(), 2); + + // + // CHANGE DEFAULT + // + assertTrue(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault()); + assertFalse(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault()); + killBillClient.updateDefaultPaymentMethod(accountJson.getAccountId(), paymentMethodPP.getPaymentMethodId(), createdBy, reason, comment); + assertTrue(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault()); + assertFalse(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault()); + + // + // DELETE NON DEFAULT PM + // + killBillClient.deletePaymentMethod(paymentMethodCC.getPaymentMethodId(), false, createdBy, reason, comment); + + // + // FETCH ALL PAYMENT METHODS + // + paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId()); + assertEquals(paymentMethods.size(), 1); + + // + // DELETE DEFAULT PAYMENT METHOD (without special flag first) + // + try { + killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), false, createdBy, reason, comment); + fail(); + } catch (final KillBillClientException e) { + } + + // + // RETRY TO DELETE DEFAULT PAYMENT METHOD (with special flag this time) + // + killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), true, createdBy, reason, comment); + + // CHECK ACCOUNT IS NOW AUTO_PAY_OFF + final List tagsJson = killBillClient.getAccountTags(accountJson.getAccountId()); + Assert.assertEquals(tagsJson.size(), 1); + final Tag tagJson = tagsJson.get(0); + Assert.assertEquals(tagJson.getTagDefinitionName(), "AUTO_PAY_OFF"); + Assert.assertEquals(tagJson.getTagDefinitionId(), new UUID(0, 1)); + + // FETCH ACCOUNT AGAIN AND CHECK THERE IS NO DEFAULT PAYMENT METHOD SET + final Account updatedAccount = killBillClient.getAccount(accountJson.getAccountId()); + Assert.assertEquals(updatedAccount.getAccountId(), accountJson.getAccountId()); + Assert.assertNull(updatedAccount.getPaymentMethodId()); + + // + // FINALLY TRY TO REMOVE AUTO_PAY_OFF WITH NO DEFAULT PAYMENT METHOD ON ACCOUNT + // + try { + killBillClient.deleteAccountTag(accountJson.getAccountId(), new UUID(0, 1), createdBy, reason, comment); + } catch (final KillBillClientException e) { + } + } + + @Test(groups = "slow") + public void testAccountPaymentsWithRefund() throws Exception { + final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice(); + + // Verify payments + final InvoicePayments objFromJson = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId()); + Assert.assertEquals(objFromJson.size(), 1); + } + + @Test(groups = "slow", description = "Add tags to account") + public void testTags() throws Exception { + final Account input = createAccount(); + // Use tag definition for AUTO_PAY_OFF + final UUID autoPayOffId = new UUID(0, 1); + + // Add a tag + killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment); + + // Retrieves all tags + final List tags1 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL); + Assert.assertEquals(tags1.size(), 1); + Assert.assertEquals(tags1.get(0).getTagDefinitionId(), autoPayOffId); + + // Verify adding the same tag a second time doesn't do anything + killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment); + + // Retrieves all tags again + killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment); + final List tags2 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL); + Assert.assertEquals(tags2, tags1); + + // Verify audit logs + Assert.assertEquals(tags2.get(0).getAuditLogs().size(), 1); + final AuditLog auditLogJson = tags2.get(0).getAuditLogs().get(0); + Assert.assertEquals(auditLogJson.getChangeType(), "INSERT"); + Assert.assertEquals(auditLogJson.getChangedBy(), createdBy); + Assert.assertEquals(auditLogJson.getReasonCode(), reason); + Assert.assertEquals(auditLogJson.getComments(), comment); + Assert.assertNotNull(auditLogJson.getChangeDate()); + Assert.assertNotNull(auditLogJson.getUserToken()); + } + + @Test(groups = "slow", description = "Add custom fields to account") + public void testCustomFields() throws Exception { + final Account accountJson = createAccount(); + assertNotNull(accountJson); + + final Collection customFields = new LinkedList(); + customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "1", "value1", null)); + customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "2", "value2", null)); + customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "3", "value3", null)); + + killBillClient.createAccountCustomFields(accountJson.getAccountId(), customFields, createdBy, reason, comment); + + final List accountCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId()); + assertEquals(accountCustomFields.size(), 3); + + // Delete all custom fields for account + killBillClient.deleteAccountCustomFields(accountJson.getAccountId(), createdBy, reason, comment); + + final List remainingCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId()); + assertEquals(remainingCustomFields.size(), 0); + } + + @Test(groups = "slow", description = "Can paginate through all accounts") + public void testAccountsPagination() throws Exception { + for (int i = 0; i < 5; i++) { + createAccount(); + } + + final Accounts allAccounts = killBillClient.getAccounts(); + Assert.assertEquals(allAccounts.size(), 5); + + Accounts page = killBillClient.getAccounts(0L, 1L); + for (int i = 0; i < 5; i++) { + Assert.assertNotNull(page); + Assert.assertEquals(page.size(), 1); + Assert.assertEquals(page.get(0), allAccounts.get(i)); + page = page.getNext(); + } + Assert.assertNull(page); + } + + private void searchAccount(final Account input, @Nullable final Account output) throws Exception { + // Search by id + if (output != null) { + doSearchAccount(input.getAccountId().toString(), output); + } + + // Search by name + doSearchAccount(input.getName(), output); + + // Search by email + doSearchAccount(input.getEmail(), output); + + // Search by company name + doSearchAccount(input.getCompany(), output); + + // Search by external key. + // Note: we will always find a match since we don't update it + final List accountsByExternalKey = killBillClient.searchAccounts(input.getExternalKey()); + Assert.assertEquals(accountsByExternalKey.size(), 1); + Assert.assertEquals(accountsByExternalKey.get(0).getAccountId(), input.getAccountId()); + Assert.assertEquals(accountsByExternalKey.get(0).getExternalKey(), input.getExternalKey()); + } + + private void doSearchAccount(final String key, @Nullable final Account output) throws Exception { + final List accountsByKey = killBillClient.searchAccounts(key); + if (output == null) { + Assert.assertEquals(accountsByKey.size(), 0); + } else { + Assert.assertEquals(accountsByKey.size(), 1); + Assert.assertEquals(accountsByKey.get(0), output); + } + } +} From 3a8e3f98d07e18588946bd7d995f03c314fbe5f6 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Mon, 17 Aug 2015 19:22:04 -0700 Subject: [PATCH 019/137] Remove unused configuration file --- .../resources/killbill-server.properties.mos | 88 ------------------- 1 file changed, 88 deletions(-) delete mode 100644 profiles/killbill/src/main/resources/killbill-server.properties.mos diff --git a/profiles/killbill/src/main/resources/killbill-server.properties.mos b/profiles/killbill/src/main/resources/killbill-server.properties.mos deleted file mode 100644 index 735f895f95..0000000000 --- a/profiles/killbill/src/main/resources/killbill-server.properties.mos +++ /dev/null @@ -1,88 +0,0 @@ -# -# Copyright 2010-2013 Ning, Inc. -# Copyright 2014 Groupon, Inc -# Copyright 2014 The Billing Project, LLC -# -# The Billing Project licenses this file to you 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. -# - -# -# KILLBILL GENERIC PROPERTIES -# -# Database config -#org.killbill.dao.url=jdbc:mysql://127.0.0.1:3306/killbill -org.killbill.dao.url=jdbc:mysql://127.0.0.1:3306/killbil_analytics_mos -org.killbill.dao.user=root -org.killbill.dao.password=root -org.killbill.dao.logLevel=DEBUG - -# Use the SpyCarAdvanced.xml catalog -#org.killbill.catalog.uri=SpyCarAdvanced.xml -org.killbill.catalog.uri=CatalogMos.xml - -# NotificationQ, Bus, ExtBus config -org.killbill.notificationq.main.sleep=1000 -org.killbill.notificationq.main.claimed=10 -org.killbill.notificationq.main.sticky=true - -org.killbill.persistent.bus.external.sticky=true -org.killbill.persistent.bus.external.inMemory=true - -org.killbill.persistent.bus.main.sticky=true -org.killbill.persistent.bus.main.claimed=1 -org.killbill.persistent.bus.main.inflight.claimed=1 -org.killbill.persistent.bus.main.nbThreads=1 -org.killbill.persistent.bus.main.sleep=0 -org.killbill.persistent.bus.main.useInflightQ=true -org.killbill.persistent.bus.main.queue.capacity=100 - -# Start KB in multi-tenant -org.killbill.server.multitenant=true - -# Override polling from Tenant Broadcast Task -org.killbill.tenant.broadcast.rate=1s - -# -# PLUGIN SPECIFIC PROPERTIES -# -# Database config (OSGI plugins) -#org.killbill.billing.osgi.dao.url=jdbc:mysql://127.0.0.1:3306/killbill -org.killbill.billing.osgi.dao.url=jdbc:mysql://127.0.0.1:3306/killbil_analytics_mos -org.killbill.billing.osgi.dao.user=root -org.killbill.billing.osgi.dao.password=root - -# Allow jruby concurrency -org.killbill.jruby.context.scope=THREADSAFE - -# Path for plugin config -#org.killbill.billing.osgi.bundles.jruby.conf.dir=/var/tmp/bundles/plugins/config -org.killbill.osgi.bundle.install.dir=/var/tmp/bundles_analytics_mos - -# Config property files for plugin to access -org.killbill.server.properties=/Users/sbrossier/Src/killbill/killbill/profiles/killbill/src/main/resources/killbill-server.properties - -# -# INTEGRATION TESTS ONLY -# -# To enable test endpoint and have Kill Bill run with a ClockMock (should not be used for production server) -org.killbill.server.test.mode=true - -# Set payment calls to timeout after 5 sec -- mostly for integration tests -org.killbill.payment.plugin.timeout=5s - -org.killbill.payment.retry.days= - -org.killbill.catalog.bundlePath=CatalogTranslation -org.killbill.template.bundlePath=InvoiceTranslation -org.killbill.template.name=HtmlInvoiceTemplate.mustache - From d84d3bd8145e4a73da773ba0831a50d963ecb854 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 18 Aug 2015 11:26:35 -0700 Subject: [PATCH 020/137] Add missing queue properties in default killbill-server.properties --- .../killbill/src/main/resources/killbill-server.properties | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/profiles/killbill/src/main/resources/killbill-server.properties b/profiles/killbill/src/main/resources/killbill-server.properties index c4c568ec60..734d17b63d 100644 --- a/profiles/killbill/src/main/resources/killbill-server.properties +++ b/profiles/killbill/src/main/resources/killbill-server.properties @@ -29,9 +29,10 @@ org.killbill.dao.logLevel=DEBUG org.killbill.catalog.uri=SpyCarAdvanced.xml # NotificationQ, Bus, ExtBus config -org.killbill.notificationq.main.sleep=1000 -org.killbill.notificationq.main.claimed=10 +org.killbill.notificationq.main.sleep=100 +org.killbill.notificationq.main.claimed=1 org.killbill.notificationq.main.queue.mode=STICKY_POLLING +org.killbill.notificationq.main.notification.nbThreads=1 org.killbill.persistent.bus.external.queue.mode=STICKY_EVENTS org.killbill.persistent.bus.external.inMemory=true From 98c09ec4e6093785aa64eddddd9f19b9b61f9095 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 18 Aug 2015 14:48:52 -0700 Subject: [PATCH 021/137] Update to released OSS version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 935cc94654..6b6b458520 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.36-SNAPSHOT + 0.37 killbill 0.15.3-SNAPSHOT From 17581aeba9a21420364d97f772f78baf51372128 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 18 Aug 2015 14:56:03 -0700 Subject: [PATCH 022/137] util: don't trust user-specified UUID in CustomFieldModelDao Move the UUID generation in CustomFieldModelDao. This not only fixes an NPE (when a plugin doesn't specify a UUID in the API call for example), but also makes sure we're using our UUIDs wrapper to generate the id. See https://github.com/killbill/killbill/issues/35. Signed-off-by: Pierre-Alexandre Meyer --- .../billing/account/dao/TestAccountDao.java | 9 ++++----- .../customfield/api/DefaultCustomFieldUserApi.java | 2 +- .../util/customfield/dao/CustomFieldModelDao.java | 8 +++----- .../billing/util/customfield/TestFieldStore.java | 13 ++++++------- .../api/TestDefaultCustomFieldUserApi.java | 5 ++++- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java b/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java index 1ead0697bc..70a3947225 100644 --- a/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java +++ b/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java @@ -1,7 +1,9 @@ /* * Copyright 2010-2013 Ning, Inc. + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * - * Ning licenses this file to you under the Apache License, version 2.0 + * The Billing Project licenses this file to you 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: * @@ -41,8 +43,6 @@ import org.killbill.billing.util.audit.AuditLog; import org.killbill.billing.util.audit.ChangeType; import org.killbill.billing.util.audit.DefaultAccountAuditLogs; -import org.killbill.billing.util.customfield.CustomField; -import org.killbill.billing.util.customfield.StringCustomField; import org.killbill.billing.util.customfield.dao.CustomFieldModelDao; import org.killbill.billing.util.dao.TableName; import org.killbill.billing.util.entity.Pagination; @@ -169,8 +169,7 @@ public void testCustomFields() throws CustomFieldApiException { final String fieldName = UUID.randomUUID().toString().substring(0, 4); final String fieldValue = UUID.randomUUID().toString(); - final CustomField field = new StringCustomField(fieldName, fieldValue, ObjectType.ACCOUNT, accountId, internalCallContext.getCreatedDate()); - customFieldDao.create(new CustomFieldModelDao(field), internalCallContext); + customFieldDao.create(new CustomFieldModelDao(internalCallContext.getCreatedDate(), fieldName, fieldValue, accountId, ObjectType.ACCOUNT), internalCallContext); final List customFieldMap = customFieldDao.getCustomFieldsForObject(accountId, ObjectType.ACCOUNT, internalCallContext); Assert.assertEquals(customFieldMap.size(), 1); diff --git a/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java b/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java index ad05b91959..5abac2849b 100644 --- a/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java +++ b/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java @@ -119,7 +119,7 @@ public boolean apply(final CustomFieldModelDao input) { } for (CustomField cur : toBeInserted) { - customFieldDao.create(new CustomFieldModelDao(cur), internalCallContextFactory.createInternalCallContext(cur.getObjectId(), cur.getObjectType(), context)); + customFieldDao.create(new CustomFieldModelDao(context.getCreatedDate(), cur.getFieldName(), cur.getFieldValue(), cur.getObjectId(), cur.getObjectType()), internalCallContextFactory.createInternalCallContext(cur.getObjectId(), cur.getObjectType(), context)); } } diff --git a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java index 7a96c922dd..9f4371be9b 100644 --- a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java +++ b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java @@ -19,11 +19,10 @@ import java.util.UUID; import org.joda.time.DateTime; - import org.killbill.billing.ObjectType; +import org.killbill.billing.util.UUIDs; import org.killbill.billing.util.customfield.CustomField; import org.killbill.billing.util.dao.TableName; -import org.killbill.billing.entity.EntityBase; import org.killbill.billing.util.entity.dao.EntityModelDao; import org.killbill.billing.util.entity.dao.EntityModelDaoBase; @@ -48,9 +47,8 @@ public CustomFieldModelDao(final UUID id, final DateTime createdDate, final Date this.isActive = true; } - public CustomFieldModelDao(final CustomField customField) { - this(customField.getId(), customField.getCreatedDate(), customField.getUpdatedDate(), customField.getFieldName(), - customField.getFieldValue(), customField.getObjectId(), customField.getObjectType()); + public CustomFieldModelDao(final DateTime createdDate, final String fieldName, final String fieldValue, final UUID objectId, final ObjectType objectType) { + this(UUIDs.randomUUID(), createdDate, createdDate, fieldName, fieldValue, objectId, objectType); } public String getFieldName() { diff --git a/util/src/test/java/org/killbill/billing/util/customfield/TestFieldStore.java b/util/src/test/java/org/killbill/billing/util/customfield/TestFieldStore.java index 9051563f2a..9eed9861e2 100644 --- a/util/src/test/java/org/killbill/billing/util/customfield/TestFieldStore.java +++ b/util/src/test/java/org/killbill/billing/util/customfield/TestFieldStore.java @@ -1,7 +1,9 @@ /* * Copyright 2010-2011 Ning, Inc. + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * - * Ning licenses this file to you under the Apache License, version 2.0 + * The Billing Project licenses this file to you 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: * @@ -18,13 +20,12 @@ import java.util.UUID; -import org.testng.annotations.Test; - import org.killbill.billing.ObjectType; import org.killbill.billing.api.TestApiListener.NextEvent; import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB; import org.killbill.billing.util.api.CustomFieldApiException; import org.killbill.billing.util.customfield.dao.CustomFieldModelDao; +import org.testng.annotations.Test; public class TestFieldStore extends UtilTestSuiteWithEmbeddedDB { @@ -36,16 +37,14 @@ public void testCreateCustomField() throws CustomFieldApiException { String fieldName = "TestField1"; String fieldValue = "Kitty Hawk"; - final CustomField field = new StringCustomField(fieldName, fieldValue, objectType, id, internalCallContext.getCreatedDate()); eventsListener.pushExpectedEvent(NextEvent.CUSTOM_FIELD); - customFieldDao.create(new CustomFieldModelDao(field), internalCallContext); + customFieldDao.create(new CustomFieldModelDao(internalCallContext.getCreatedDate(), fieldName, fieldValue, id, objectType), internalCallContext); assertListenerStatus(); fieldName = "TestField2"; fieldValue = "Cape Canaveral"; - final CustomField field2 = new StringCustomField(fieldName, fieldValue, objectType, id, internalCallContext.getCreatedDate()); eventsListener.pushExpectedEvent(NextEvent.CUSTOM_FIELD); - customFieldDao.create(new CustomFieldModelDao(field2), internalCallContext); + customFieldDao.create(new CustomFieldModelDao(internalCallContext.getCreatedDate(), fieldName, fieldValue, id, objectType), internalCallContext); assertListenerStatus(); } } diff --git a/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java b/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java index 3be0174387..5a60177187 100644 --- a/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java +++ b/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java @@ -62,7 +62,10 @@ public Void withHandle(final Handle handle) throws Exception { // Verify the field was saved final List customFields = customFieldUserApi.getCustomFieldsForObject(accountId, ObjectType.ACCOUNT, callContext); Assert.assertEquals(customFields.size(), 1); - Assert.assertEquals(customFields.get(0), customField); + Assert.assertEquals(customFields.get(0).getFieldName(), customField.getFieldName()); + Assert.assertEquals(customFields.get(0).getFieldValue(), customField.getFieldValue()); + Assert.assertEquals(customFields.get(0).getObjectId(), customField.getObjectId()); + Assert.assertEquals(customFields.get(0).getObjectType(), customField.getObjectType()); // Verify the account_record_id was populated dbi.withHandle(new HandleCallback() { @Override From 6de3d1ddab15c51ef59cd36580f1222847cf767b Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 18 Aug 2015 19:22:50 -0700 Subject: [PATCH 023/137] Fixes #375 --- .../timeline/SubscriptionBaseTimeline.java | 22 +- .../timeline/SubscriptionBaseTimelineApi.java | 12 - .../integration/TestRepairIntegration.java | 360 --------- .../api/timeline/DefaultDeletedEvent.java | 42 - .../api/timeline/DefaultNewEvent.java | 58 -- .../DefaultRepairSubscriptionEvent.java | 120 --- .../DefaultSubscriptionBaseTimeline.java | 136 +--- .../DefaultSubscriptionBaseTimelineApi.java | 394 +--------- .../RepairSubscriptionApiService.java | 55 -- .../RepairSubscriptionLifecycleDao.java | 30 - .../api/timeline/SubscriptionDataRepair.java | 228 ------ .../engine/dao/DefaultSubscriptionDao.java | 41 - .../engine/dao/RepairSubscriptionDao.java | 350 --------- .../engine/dao/SubscriptionDao.java | 4 - .../glue/DefaultSubscriptionModule.java | 12 - .../subscription/api/TestEventJson.java | 13 - .../api/timeline/TestRepairBP.java | 702 ----------------- .../api/timeline/TestRepairWithAO.java | 726 ------------------ .../api/timeline/TestRepairWithError.java | 421 ---------- .../api/user/TestSubscriptionHelper.java | 287 ------- .../engine/dao/MockSubscriptionDaoMemory.java | 6 - .../TestDefaultSubscriptionModuleNoDB.java | 7 - ...faultSubscriptionModuleWithEmbeddedDB.java | 7 - 23 files changed, 18 insertions(+), 4015 deletions(-) delete mode 100644 beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestRepairIntegration.java delete mode 100644 subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultDeletedEvent.java delete mode 100644 subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultNewEvent.java delete mode 100644 subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultRepairSubscriptionEvent.java delete mode 100644 subscription/src/main/java/org/killbill/billing/subscription/api/timeline/RepairSubscriptionApiService.java delete mode 100644 subscription/src/main/java/org/killbill/billing/subscription/api/timeline/RepairSubscriptionLifecycleDao.java delete mode 100644 subscription/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionDataRepair.java delete mode 100644 subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java delete mode 100644 subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairBP.java delete mode 100644 subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithAO.java delete mode 100644 subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithError.java diff --git a/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java b/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java index 855e0a1971..a0a981f38a 100644 --- a/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java +++ b/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java @@ -30,20 +30,9 @@ /** * The interface {@code} shows a view of all the events for a particular {@code SubscriptionBase}. *

- * It can be used to display information, or it can be used to modify the subscription stream of events - * and 'repair' the stream by versioning the events. */ public interface SubscriptionBaseTimeline extends Entity { - /** - * @return the list of events that should be deleted when repairing the stream. - */ - public List getDeletedEvents(); - - /** - * @return the list of events that should be added when repairing the stream - */ - public List getNewEvents(); /** * @return the current list of events for that {@code SubscriptionBase} @@ -56,17 +45,16 @@ public interface SubscriptionBaseTimeline extends Entity { public long getActiveVersion(); - public interface DeletedEvent { + + public interface ExistingEvent { /** * @return the unique if for the event to delete */ public UUID getEventId(); - } - - public interface NewEvent { /** + * * @return the description for the event to be added */ public PlanPhaseSpecifier getPlanPhaseSpecifier(); @@ -81,10 +69,6 @@ public interface NewEvent { */ public SubscriptionBaseTransitionType getSubscriptionTransitionType(); - } - - public interface ExistingEvent extends DeletedEvent, NewEvent { - /** * @return the date at which this event was effective */ diff --git a/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimelineApi.java b/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimelineApi.java index ae0a8617ea..1d96853cf5 100644 --- a/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimelineApi.java +++ b/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimelineApi.java @@ -16,23 +16,11 @@ package org.killbill.billing.subscription.api.timeline; -import java.util.UUID; - import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle; -import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.TenantContext; public interface SubscriptionBaseTimelineApi { public BundleBaseTimeline getBundleTimeline(SubscriptionBaseBundle bundle, TenantContext context) throws SubscriptionBaseRepairException; - - public BundleBaseTimeline getBundleTimeline(UUID accountId, String bundleName, TenantContext context) - throws SubscriptionBaseRepairException; - - public BundleBaseTimeline getBundleTimeline(UUID bundleId, TenantContext context) - throws SubscriptionBaseRepairException; - - public BundleBaseTimeline repairBundle(BundleBaseTimeline input, boolean dryRun, CallContext context) - throws SubscriptionBaseRepairException; } diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestRepairIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestRepairIntegration.java deleted file mode 100644 index 5c9b10410a..0000000000 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestRepairIntegration.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * Copyright 2014-2015 Groupon, Inc - * Copyright 2014-2015 The Billing Project, LLC - * - * The Billing Project licenses this file to you 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 org.killbill.billing.beatrix.integration; - -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -import org.joda.time.DateTime; -import org.joda.time.Interval; -import org.testng.Assert; -import org.testng.annotations.Test; - -import org.killbill.billing.account.api.Account; -import org.killbill.billing.api.TestApiListener.NextEvent; -import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.catalog.api.PhaseType; -import org.killbill.billing.catalog.api.PlanPhaseSpecifier; -import org.killbill.billing.catalog.api.PriceListSet; -import org.killbill.billing.catalog.api.ProductCategory; -import org.killbill.billing.entitlement.api.DefaultEntitlement; -import org.killbill.billing.entitlement.api.Entitlement.EntitlementState; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.subscription.api.timeline.BundleBaseTimeline; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.DeletedEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.ExistingEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.NewEvent; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; -import org.killbill.billing.subscription.api.user.SubscriptionEvents; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -public class TestRepairIntegration extends TestIntegrationBase { - - @Test(groups = "slow", enabled = false) - public void testRepairChangeBPWithAddonIncludedIntrial() throws Exception { - log.info("Starting testRepairChangeBPWithAddonIncludedIntrial"); - testRepairChangeBPWithAddonIncluded(true); - } - - @Test(groups = "slow", enabled = false) - public void testRepairChangeBPWithAddonIncludedOutOfTrial() throws Exception { - log.info("Starting testRepairChangeBPWithAddonIncludedOutOfTrial"); - testRepairChangeBPWithAddonIncluded(false); - } - - private void testRepairChangeBPWithAddonIncluded(final boolean inTrial) throws Exception { - - final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0); - clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis()); - - final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(25)); - - final String productName = "Shotgun"; - final BillingPeriod term = BillingPeriod.MONTHLY; - final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME; - - final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE); - assertNotNull(bpEntitlement); - - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final DefaultEntitlement aoEntitlement1 = addAOEntitlementAndCheckForCompletion(bpEntitlement.getBundleId(), "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT); - final DefaultEntitlement aoEntitlement2 = addAOEntitlementAndCheckForCompletion(bpEntitlement.getBundleId(), "Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT); - - // MOVE CLOCK A LITTLE BIT MORE -- EITHER STAY IN TRIAL OR GET OUT - final int duration = inTrial ? 3 : 35; - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(duration)); - if (!inTrial) { - busHandler.pushExpectedEvent(NextEvent.PHASE); - busHandler.pushExpectedEvent(NextEvent.PHASE); - busHandler.pushExpectedEvent(NextEvent.PHASE); - busHandler.pushExpectedEvent(NextEvent.INVOICE); - busHandler.pushExpectedEvent(NextEvent.PAYMENT); - } - clock.addDeltaFromReality(it.toDurationMillis()); - if (!inTrial) { - assertListenerStatus(); - } - final boolean ifRepair = false; - if (ifRepair) { - BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bpEntitlement.getSubscriptionBase().getBundleId(), callContext); - sortEventsOnBundle(bundleRepair); - - // Quick check - SubscriptionBaseTimeline bpRepair = getSubscriptionRepair(bpEntitlement.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - final SubscriptionBaseTimeline aoRepair = getSubscriptionRepair(aoEntitlement1.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final SubscriptionBaseTimeline aoRepair2 = getSubscriptionRepair(aoEntitlement2.getId(), bundleRepair); - assertEquals(aoRepair2.getExistingEvents().size(), 2); - - final DateTime bpChangeDate = clock.getUTCNow().minusDays(1); - - final List des = new LinkedList(); - des.add(createDeletedEvent(bpRepair.getExistingEvents().get(1).getEventId())); - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL); - final NewEvent ne = createNewEvent(SubscriptionBaseTransitionType.CHANGE, bpChangeDate, spec); - - bpRepair = createSubscriptionReapir(bpEntitlement.getId(), des, Collections.singletonList(ne)); - - bundleRepair = createBundleRepair(bpEntitlement.getSubscriptionBase().getBundleId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair)); - - // TIME TO REPAIR - busHandler.pushExpectedEvent(NextEvent.INVOICE); - busHandler.pushExpectedEvent(NextEvent.PAYMENT); - busHandler.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - repairApi.repairBundle(bundleRepair, false, callContext); - assertListenerStatus(); - - final DefaultSubscriptionBase newAoSubscription = (DefaultSubscriptionBase) aoEntitlement1.getSubscriptionBase(); - assertEquals(newAoSubscription.getState(), EntitlementState.CANCELLED); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - final DefaultSubscriptionBase newAoSubscription2 = (DefaultSubscriptionBase) aoEntitlement2.getSubscriptionBase(); - assertEquals(newAoSubscription2.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription2.getAllTransitions().size(), 2); - assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - final DefaultSubscriptionBase newBaseSubscription = (DefaultSubscriptionBase) bpEntitlement.getSubscriptionBase(); - assertEquals(newBaseSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newBaseSubscription.getAllTransitions().size(), 3); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - assertListenerStatus(); - } - - checkNoMoreInvoiceToGenerate(account); - } - - protected SubscriptionBaseTimeline createSubscriptionReapir(final UUID id, final List deletedEvents, final List newEvents) { - return new SubscriptionBaseTimeline() { - @Override - public UUID getId() { - return id; - } - - @Override - public DateTime getCreatedDate() { - return null; - } - - @Override - public DateTime getUpdatedDate() { - return null; - } - - @Override - public List getNewEvents() { - return newEvents; - } - - @Override - public List getExistingEvents() { - return null; - } - - @Override - public List getDeletedEvents() { - return deletedEvents; - } - - @Override - public long getActiveVersion() { - return 0; - } - }; - } - - protected BundleBaseTimeline createBundleRepair(final UUID bundleId, final String viewId, final List subscriptionRepair) { - return new BundleBaseTimeline() { - @Override - public String getViewId() { - return viewId; - } - - @Override - public List getSubscriptions() { - return subscriptionRepair; - } - - @Override - public UUID getId() { - return bundleId; - } - - @Override - public DateTime getCreatedDate() { - return null; - } - - @Override - public DateTime getUpdatedDate() { - return null; - } - - @Override - public String getExternalKey() { - return null; - } - }; - } - - protected ExistingEvent createExistingEventForAssertion(final SubscriptionBaseTransitionType type, - final String productName, final PhaseType phaseType, final ProductCategory category, final String priceListName, final BillingPeriod billingPeriod, - final DateTime effectiveDateTime) { - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType); - final ExistingEvent ev = new ExistingEvent() { - @Override - public SubscriptionBaseTransitionType getSubscriptionTransitionType() { - return type; - } - - @Override - public DateTime getRequestedDate() { - return null; - } - - @Override - public PlanPhaseSpecifier getPlanPhaseSpecifier() { - return spec; - } - - @Override - public UUID getEventId() { - return null; - } - - @Override - public DateTime getEffectiveDate() { - return effectiveDateTime; - } - - @Override - public String getPlanName() { - return null; - } - - @Override - public String getPlanPhaseName() { - return null; - } - }; - return ev; - } - - protected SubscriptionBaseTimeline getSubscriptionRepair(final UUID id, final BundleBaseTimeline bundleRepair) { - for (final SubscriptionBaseTimeline cur : bundleRepair.getSubscriptions()) { - if (cur.getId().equals(id)) { - return cur; - } - } - Assert.fail("Failed to find SubscriptionReapir " + id); - return null; - } - - protected void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) { - - log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName())); - assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()); - log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType())); - assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()); - log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory())); - assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()); - log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName())); - assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()); - log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod())); - assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()); - log.info(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate())); - assertEquals(input.getEffectiveDate(), expected.getEffectiveDate()); - } - - protected DeletedEvent createDeletedEvent(final UUID eventId) { - return new DeletedEvent() { - @Override - public UUID getEventId() { - return eventId; - } - }; - } - - protected NewEvent createNewEvent(final SubscriptionBaseTransitionType type, final DateTime requestedDate, final PlanPhaseSpecifier spec) { - - return new NewEvent() { - @Override - public SubscriptionBaseTransitionType getSubscriptionTransitionType() { - return type; - } - - @Override - public DateTime getRequestedDate() { - return requestedDate; - } - - @Override - public PlanPhaseSpecifier getPlanPhaseSpecifier() { - return spec; - } - }; - } - - protected void sortEventsOnBundle(final BundleBaseTimeline bundle) { - if (bundle.getSubscriptions() == null) { - return; - } - for (final SubscriptionBaseTimeline cur : bundle.getSubscriptions()) { - if (cur.getExistingEvents() != null) { - sortExistingEvent(cur.getExistingEvents()); - } - if (cur.getNewEvents() != null) { - sortNewEvent(cur.getNewEvents()); - } - } - } - - protected void sortExistingEvent(final List events) { - Collections.sort(events, new Comparator() { - @Override - public int compare(final ExistingEvent arg0, final ExistingEvent arg1) { - return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate()); - } - }); - } - - protected void sortNewEvent(final List events) { - Collections.sort(events, new Comparator() { - @Override - public int compare(final NewEvent arg0, final NewEvent arg1) { - return arg0.getRequestedDate().compareTo(arg1.getRequestedDate()); - } - }); - } -} diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultDeletedEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultDeletedEvent.java deleted file mode 100644 index 506cd7a0e5..0000000000 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultDeletedEvent.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.UUID; - -import org.joda.time.DateTime; - -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.DeletedEvent; - -public class DefaultDeletedEvent implements DeletedEvent { - - private final UUID id; - private final DateTime effectiveDate; - - public DefaultDeletedEvent(final UUID id, final DateTime effectiveDate) { - this.id = id; - this.effectiveDate = effectiveDate; - } - - @Override - public UUID getEventId() { - return id; - } - - public DateTime getEffectiveDate() { - return effectiveDate; - } -} diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultNewEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultNewEvent.java deleted file mode 100644 index 79303a6c1d..0000000000 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultNewEvent.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.UUID; - -import org.joda.time.DateTime; - -import org.killbill.billing.catalog.api.PlanPhaseSpecifier; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.NewEvent; - -public class DefaultNewEvent implements NewEvent { - - private final UUID subscriptionId; - private final PlanPhaseSpecifier spec; - private final DateTime requestedDate; - private final SubscriptionBaseTransitionType transitionType; - - public DefaultNewEvent(final UUID subscriptionId, final PlanPhaseSpecifier spec, final DateTime requestedDate, final SubscriptionBaseTransitionType transitionType) { - this.subscriptionId = subscriptionId; - this.spec = spec; - this.requestedDate = requestedDate; - this.transitionType = transitionType; - } - - @Override - public PlanPhaseSpecifier getPlanPhaseSpecifier() { - return spec; - } - - @Override - public DateTime getRequestedDate() { - return requestedDate; - } - - @Override - public SubscriptionBaseTransitionType getSubscriptionTransitionType() { - return transitionType; - } - - public UUID getSubscriptionId() { - return subscriptionId; - } -} diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultRepairSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultRepairSubscriptionEvent.java deleted file mode 100644 index 54391750e0..0000000000 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultRepairSubscriptionEvent.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.UUID; - -import org.joda.time.DateTime; - -import org.killbill.billing.events.BusEventBase; -import org.killbill.billing.events.RepairSubscriptionInternalEvent; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class DefaultRepairSubscriptionEvent extends BusEventBase implements RepairSubscriptionInternalEvent { - - private final UUID bundleId; - private final UUID accountId; - private final DateTime effectiveDate; - - - @JsonCreator - public DefaultRepairSubscriptionEvent(@JsonProperty("accountId") final UUID accountId, - @JsonProperty("bundleId") final UUID bundleId, - @JsonProperty("effectiveDate") final DateTime effectiveDate, - @JsonProperty("searchKey1") final Long searchKey1, - @JsonProperty("searchKey2") final Long searchKey2, - @JsonProperty("userToken") final UUID userToken) { - super(searchKey1, searchKey2, userToken); - this.bundleId = bundleId; - this.accountId = accountId; - this.effectiveDate = effectiveDate; - } - - @JsonIgnore - @Override - public BusInternalEventType getBusEventType() { - return BusInternalEventType.BUNDLE_REPAIR; - } - - @Override - public UUID getBundleId() { - return bundleId; - } - - @Override - public UUID getAccountId() { - return accountId; - } - - @Override - public DateTime getEffectiveDate() { - return effectiveDate; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((accountId == null) ? 0 : accountId.hashCode()); - result = prime * result - + ((bundleId == null) ? 0 : bundleId.hashCode()); - result = prime * result - + ((effectiveDate == null) ? 0 : effectiveDate.hashCode()); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final DefaultRepairSubscriptionEvent other = (DefaultRepairSubscriptionEvent) obj; - if (accountId == null) { - if (other.accountId != null) { - return false; - } - } else if (!accountId.equals(other.accountId)) { - return false; - } - if (bundleId == null) { - if (other.bundleId != null) { - return false; - } - } else if (!bundleId.equals(other.bundleId)) { - return false; - } - if (effectiveDate == null) { - if (other.effectiveDate != null) { - return false; - } - } else if (effectiveDate.compareTo(other.effectiveDate) != 0) { - return false; - } - return true; - } - -} diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java index e81a70367e..5b4c7d729b 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java @@ -16,7 +16,6 @@ package org.killbill.billing.subscription.api.timeline; -import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; @@ -26,7 +25,6 @@ import javax.annotation.Nullable; import org.joda.time.DateTime; - import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.catalog.api.Catalog; import org.killbill.billing.catalog.api.CatalogApiException; @@ -36,48 +34,22 @@ import org.killbill.billing.catalog.api.PlanPhaseSpecifier; import org.killbill.billing.catalog.api.ProductCategory; import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; +import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData; import org.killbill.billing.subscription.events.SubscriptionBaseEvent; import org.killbill.billing.subscription.events.phase.PhaseEvent; import org.killbill.billing.subscription.events.user.ApiEvent; import org.killbill.billing.subscription.events.user.ApiEventType; - public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline { private final UUID id; private final List existingEvents; - private final List newEvents; - private final List deletedEvents; private final long activeVersion; - public DefaultSubscriptionBaseTimeline(final UUID id, final long activeVersion) { - this.id = id; - this.activeVersion = activeVersion; - this.existingEvents = Collections.emptyList(); - this.deletedEvents = Collections.emptyList(); - this.newEvents = Collections.emptyList(); - } - - public DefaultSubscriptionBaseTimeline(final SubscriptionBaseTimeline input) { - this.id = input.getId(); - this.activeVersion = input.getActiveVersion(); - this.existingEvents = (input.getExistingEvents() != null) ? new ArrayList(input.getExistingEvents()) : - Collections.emptyList(); - sortExistingEvent(this.existingEvents); - this.deletedEvents = (input.getDeletedEvents() != null) ? new ArrayList(input.getDeletedEvents()) : - Collections.emptyList(); - this.newEvents = (input.getNewEvents() != null) ? new ArrayList(input.getNewEvents()) : - Collections.emptyList(); - sortNewEvent(this.newEvents); - } - - // CTOR for returning events only - public DefaultSubscriptionBaseTimeline(final SubscriptionDataRepair input, final Catalog catalog) throws CatalogApiException { + public DefaultSubscriptionBaseTimeline(final DefaultSubscriptionBase input, final Catalog catalog) throws CatalogApiException { this.id = input.getId(); this.existingEvents = toExistingEvents(catalog, input.getActiveVersion(), input.getCategory(), input.getEvents()); - this.deletedEvents = null; - this.newEvents = null; this.activeVersion = input.getActiveVersion(); } @@ -199,89 +171,6 @@ public String getPlanPhaseName() { return result; } - - /* - - private List toExistingEvents(final Catalog catalog, final long processingVersion, final ProductCategory category, final List events, List result) - throws CatalogApiException { - - - String prevProductName = null; - BillingPeriod prevBillingPeriod = null; - String prevPriceListName = null; - PhaseType prevPhaseType = null; - - DateTime startDate = null; - - for (final SubscriptionBaseEvent cur : events) { - - if (processingVersion != cur.getActiveVersion()) { - continue; - } - - // First active event is used to figure out which catalog version to use. - startDate = (startDate == null && cur.getActiveVersion() == processingVersion) ? cur.getEffectiveDate() : startDate; - - String productName = null; - BillingPeriod billingPeriod = null; - String priceListName = null; - PhaseType phaseType = null; - - ApiEventType apiType = null; - switch (cur.getType()) { - case PHASE: - PhaseEvent phaseEV = (PhaseEvent) cur; - phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType(); - productName = prevProductName; - billingPeriod = prevBillingPeriod; - priceListName = prevPriceListName; - break; - - case API_USER: - ApiEvent userEV = (ApiEvent) cur; - apiType = userEV.getEventType(); - Plan plan = (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null; - phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType; - productName = (plan != null) ? plan.getProduct().getName() : prevProductName; - billingPeriod = (plan != null) ? plan.getBillingPeriod() : prevBillingPeriod; - priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName; - break; - } - - final SubscriptionBaseTransitionType transitionType = SubscriptionBaseTransitionData.toSubscriptionTransitionType(cur.getType(), apiType); - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType); - result.add(new ExistingEvent() { - @Override - public SubscriptionBaseTransitionType getSubscriptionTransitionType() { - return transitionType; - } - @Override - public DateTime getRequestedDate() { - return cur.getRequestedDate(); - } - @Override - public PlanPhaseSpecifier getPlanPhaseSpecifier() { - return spec; - } - @Override - public UUID getEventId() { - return cur.getId(); - } - @Override - public DateTime getEffectiveDate() { - return cur.getEffectiveDate(); - } - }); - prevProductName = productName; - prevBillingPeriod = billingPeriod; - prevPriceListName = priceListName; - prevPhaseType = phaseType; - } - } - */ - - @Override public UUID getId() { return id; @@ -297,16 +186,6 @@ public DateTime getUpdatedDate() { throw new UnsupportedOperationException(); } - @Override - public List getDeletedEvents() { - return deletedEvents; - } - - @Override - public List getNewEvents() { - return newEvents; - } - @Override public List getExistingEvents() { return existingEvents; @@ -317,7 +196,6 @@ public long getActiveVersion() { return activeVersion; } - private void sortExistingEvent(final List events) { if (events != null) { Collections.sort(events, new Comparator() { @@ -329,14 +207,4 @@ public int compare(final ExistingEvent arg0, final ExistingEvent arg1) { } } - private void sortNewEvent(final List events) { - if (events != null) { - Collections.sort(events, new Comparator() { - @Override - public int compare(final NewEvent arg0, final NewEvent arg1) { - return arg0.getRequestedDate().compareTo(arg1.getRequestedDate()); - } - }); - } - } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java index 4171cc09e5..065830290e 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java @@ -16,111 +16,60 @@ package org.killbill.billing.subscription.api.timeline; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.UUID; -import javax.annotation.Nullable; - import org.joda.time.DateTime; - import org.killbill.billing.ErrorCode; -import org.killbill.billing.catalog.api.Catalog; +import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.CatalogApiException; import org.killbill.billing.catalog.api.CatalogService; -import org.killbill.billing.catalog.api.ProductCategory; import org.killbill.billing.subscription.api.SubscriptionApiBase; +import org.killbill.billing.subscription.api.SubscriptionBase; import org.killbill.billing.subscription.api.SubscriptionBaseApiService; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; +import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle; import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle; -import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition; -import org.killbill.billing.subscription.api.user.SubscriptionBuilder; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; -import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData; -import org.killbill.billing.subscription.engine.addon.AddonUtils; import org.killbill.billing.subscription.engine.dao.SubscriptionDao; import org.killbill.billing.subscription.events.SubscriptionBaseEvent; -import org.killbill.billing.subscription.glue.DefaultSubscriptionModule; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.NewEvent; -import org.killbill.billing.subscription.api.SubscriptionBase; -import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.InternalCallContextFactory; -import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.util.callcontext.TenantContext; import org.killbill.clock.Clock; -import com.google.common.base.Function; -import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; -import com.google.inject.name.Named; public class DefaultSubscriptionBaseTimelineApi extends SubscriptionApiBase implements SubscriptionBaseTimelineApi { - private final RepairSubscriptionLifecycleDao repairDao; private final CatalogService catalogService; private final InternalCallContextFactory internalCallContextFactory; - private final AddonUtils addonUtils; - - private final SubscriptionBaseApiService repairApiService; - - private enum RepairType { - BASE_REPAIR, - ADD_ON_REPAIR, - STANDALONE_REPAIR - } @Inject public DefaultSubscriptionBaseTimelineApi(final CatalogService catalogService, final SubscriptionBaseApiService apiService, - @Named(DefaultSubscriptionModule.REPAIR_NAMED) final RepairSubscriptionLifecycleDao repairDao, final SubscriptionDao dao, - @Named(DefaultSubscriptionModule.REPAIR_NAMED) final SubscriptionBaseApiService repairApiService, - final InternalCallContextFactory internalCallContextFactory, final Clock clock, final AddonUtils addonUtils) { + final SubscriptionDao dao, + final InternalCallContextFactory internalCallContextFactory, + final Clock clock) { super(dao, apiService, clock, catalogService); this.catalogService = catalogService; - this.repairDao = repairDao; this.internalCallContextFactory = internalCallContextFactory; - this.repairApiService = repairApiService; - this.addonUtils = addonUtils; } @Override public BundleBaseTimeline getBundleTimeline(final SubscriptionBaseBundle bundle, final TenantContext context) throws SubscriptionBaseRepairException { - return getBundleTimelineInternal(bundle, bundle.getExternalKey(), context); - } - - @Override - public BundleBaseTimeline getBundleTimeline(final UUID accountId, final String bundleName, final TenantContext context) - throws SubscriptionBaseRepairException { - final List bundles = dao.getSubscriptionBundlesForAccountAndKey(accountId, bundleName, internalCallContextFactory.createInternalTenantContext(context)); - final SubscriptionBaseBundle bundle = bundles.size() > 0 ? bundles.get(bundles.size() - 1) : null; - return getBundleTimelineInternal(bundle, bundleName + " [accountId= " + accountId.toString() + "]", context); - } - - @Override - public BundleBaseTimeline getBundleTimeline(final UUID bundleId, final TenantContext context) throws SubscriptionBaseRepairException { - - final SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(bundleId, internalCallContextFactory.createInternalTenantContext(context)); - return getBundleTimelineInternal(bundle, bundleId.toString(), context); - } - - private BundleBaseTimeline getBundleTimelineInternal(final SubscriptionBaseBundle bundle, final String descBundle, final TenantContext context) throws SubscriptionBaseRepairException { try { if (bundle == null) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_UNKNOWN_BUNDLE, descBundle); + throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_UNKNOWN_BUNDLE, bundle.getExternalKey()); } final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(bundle.getAccountId(), context); - final List subscriptions = convertToSubscriptionsDataRepair(dao.getSubscriptions(bundle.getId(), - ImmutableList.of(), - internalTenantContext)); + final List subscriptions = dao.getSubscriptions(bundle.getId(), + ImmutableList.of(), + internalTenantContext); if (subscriptions.size() == 0) { throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, bundle.getId()); } @@ -132,273 +81,7 @@ private BundleBaseTimeline getBundleTimelineInternal(final SubscriptionBaseBundl } } - private List convertToSubscriptionsDataRepair(List input) { - return new ArrayList(Collections2.transform(input, new Function() { - @Override - public SubscriptionDataRepair apply(@Nullable final SubscriptionBase subscription) { - return convertToSubscriptionDataRepair((DefaultSubscriptionBase) subscription); - } - })); - } - private SubscriptionDataRepair convertToSubscriptionDataRepair(DefaultSubscriptionBase input) { - return new SubscriptionDataRepair(input, repairApiService, (SubscriptionDao) repairDao, clock, addonUtils, catalogService, internalCallContextFactory); - } - - @Override - public BundleBaseTimeline repairBundle(final BundleBaseTimeline input, final boolean dryRun, final CallContext context) throws SubscriptionBaseRepairException { - final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(context); - try { - final SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(input.getId(), tenantContext); - if (bundle == null) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_UNKNOWN_BUNDLE, input.getId()); - } - - // Subscriptions are ordered with BASE subscription first-- if exists - final List subscriptions = convertToSubscriptionsDataRepair(dao.getSubscriptions(input.getId(), ImmutableList.of(), tenantContext)); - if (subscriptions.size() == 0) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, input.getId()); - } - - final String viewId = getViewId(((DefaultSubscriptionBaseBundle) bundle).getLastSysUpdateDate(), subscriptions); - if (!viewId.equals(input.getViewId())) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_VIEW_CHANGED, input.getId(), input.getViewId(), viewId); - } - - DateTime firstDeletedBPEventTime = null; - DateTime lastRemainingBPEventTime = null; - - boolean isBasePlanRecreate = false; - DateTime newBundleStartDate = null; - - SubscriptionDataRepair baseSubscriptionRepair = null; - final List addOnSubscriptionInRepair = new LinkedList(); - final List inRepair = new LinkedList(); - for (final SubscriptionBase cur : subscriptions) { - final SubscriptionBaseTimeline curRepair = findAndCreateSubscriptionRepair(cur.getId(), input.getSubscriptions()); - if (curRepair != null) { - final SubscriptionDataRepair curInputRepair = ((SubscriptionDataRepair) cur); - final List remaining = getRemainingEventsAndValidateDeletedEvents(curInputRepair, firstDeletedBPEventTime, curRepair.getDeletedEvents()); - - final boolean isPlanRecreate = (curRepair.getNewEvents().size() > 0 - && (curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionBaseTransitionType.CREATE - || curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionBaseTransitionType.RE_CREATE)); - - final DateTime newSubscriptionStartDate = isPlanRecreate ? curRepair.getNewEvents().get(0).getRequestedDate() : null; - - if (isPlanRecreate && remaining.size() != 0) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_SUB_RECREATE_NOT_EMPTY, cur.getId(), cur.getBundleId()); - } - - if (!isPlanRecreate && remaining.size() == 0) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_SUB_EMPTY, cur.getId(), cur.getBundleId()); - } - - if (cur.getCategory() == ProductCategory.BASE) { - - final int bpTransitionSize = ((DefaultSubscriptionBase) cur).getAllTransitions().size(); - lastRemainingBPEventTime = (remaining.size() > 0) ? curInputRepair.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime() : null; - firstDeletedBPEventTime = (remaining.size() < bpTransitionSize) ? curInputRepair.getAllTransitions().get(remaining.size()).getEffectiveTransitionTime() : null; - - isBasePlanRecreate = isPlanRecreate; - newBundleStartDate = newSubscriptionStartDate; - } - - if (curRepair.getNewEvents().size() > 0) { - final DateTime lastRemainingEventTime = (remaining.size() == 0) ? null : curInputRepair.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime(); - validateFirstNewEvent(curInputRepair, curRepair.getNewEvents().get(0), lastRemainingBPEventTime, lastRemainingEventTime); - } - - final SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair(curInputRepair, newBundleStartDate, newSubscriptionStartDate, remaining, tenantContext); - repairDao.initializeRepair(curInputRepair.getId(), remaining, tenantContext); - inRepair.add(curOutputRepair); - if (curOutputRepair.getCategory() == ProductCategory.ADD_ON) { - // Check if ADD_ON RE_CREATE is before BP start - if (isPlanRecreate && (subscriptions.get(0)).getStartDate().isAfter(curRepair.getNewEvents().get(0).getRequestedDate())) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_AO_CREATE_BEFORE_BP_START, cur.getId(), cur.getBundleId()); - } - addOnSubscriptionInRepair.add(curOutputRepair); - } else if (curOutputRepair.getCategory() == ProductCategory.BASE) { - baseSubscriptionRepair = curOutputRepair; - } - } - } - - final RepairType repairType = getRepairType(subscriptions.get(0), (baseSubscriptionRepair != null)); - switch (repairType) { - case BASE_REPAIR: - // We need to add any existing addon that are not in the input repair list - for (final SubscriptionBase cur : subscriptions) { - if (cur.getCategory() == ProductCategory.ADD_ON && !inRepair.contains(cur)) { - final SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair((SubscriptionDataRepair) cur, newBundleStartDate, null, - ((SubscriptionDataRepair) cur).getEvents(), tenantContext); - repairDao.initializeRepair(curOutputRepair.getId(), ((SubscriptionDataRepair) cur).getEvents(), tenantContext); - inRepair.add(curOutputRepair); - addOnSubscriptionInRepair.add(curOutputRepair); - } - } - break; - case ADD_ON_REPAIR: - // We need to set the baseSubscription as it is useful to calculate addon validity - final SubscriptionDataRepair baseSubscription = (SubscriptionDataRepair) subscriptions.get(0); - baseSubscriptionRepair = createSubscriptionDataRepair(baseSubscription, baseSubscription.getBundleStartDate(), baseSubscription.getAlignStartDate(), - baseSubscription.getEvents(), tenantContext); - break; - case STANDALONE_REPAIR: - default: - break; - } - - validateBasePlanRecreate(isBasePlanRecreate, subscriptions, input.getSubscriptions()); - validateInputSubscriptionsKnown(subscriptions, input.getSubscriptions()); - - final Collection newEvents = createOrderedNewEventInput(input.getSubscriptions()); - for (final NewEvent newEvent : newEvents) { - final DefaultNewEvent cur = (DefaultNewEvent) newEvent; - final SubscriptionDataRepair curDataRepair = findSubscriptionDataRepair(cur.getSubscriptionId(), inRepair); - if (curDataRepair == null) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_UNKNOWN_SUBSCRIPTION, cur.getSubscriptionId()); - } - curDataRepair.addNewRepairEvent(cur, baseSubscriptionRepair, addOnSubscriptionInRepair, context); - } - - if (dryRun) { - baseSubscriptionRepair.addFutureAddonCancellation(addOnSubscriptionInRepair, context); - - final List repairs = createGetSubscriptionRepairList(subscriptions, convertDataRepair(inRepair, tenantContext), tenantContext); - return createGetBundleRepair(input.getId(), bundle.getExternalKey(), input.getViewId(), repairs); - } else { - dao.repair(bundle.getAccountId(), input.getId(), inRepair, internalCallContextFactory.createInternalCallContext(bundle.getAccountId(), context)); - return getBundleTimeline(input.getId(), context); - } - } catch (CatalogApiException e) { - throw new SubscriptionBaseRepairException(e); - } finally { - repairDao.cleanup(tenantContext); - } - } - - private RepairType getRepairType(final SubscriptionBase firstSubscription, final boolean gotBaseSubscription) { - if (firstSubscription.getCategory() == ProductCategory.BASE) { - return gotBaseSubscription ? RepairType.BASE_REPAIR : RepairType.ADD_ON_REPAIR; - } else { - return RepairType.STANDALONE_REPAIR; - } - } - - private void validateBasePlanRecreate(final boolean isBasePlanRecreate, final List subscriptions, final List input) - throws SubscriptionBaseRepairException { - if (!isBasePlanRecreate) { - return; - } - if (subscriptions.size() != input.size()) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_BP_RECREATE_MISSING_AO, subscriptions.get(0).getBundleId()); - } - for (final SubscriptionBaseTimeline cur : input) { - if (cur.getNewEvents().size() != 0 - && (cur.getNewEvents().get(0).getSubscriptionTransitionType() != SubscriptionBaseTransitionType.CREATE - && cur.getNewEvents().get(0).getSubscriptionTransitionType() != SubscriptionBaseTransitionType.RE_CREATE)) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_BP_RECREATE_MISSING_AO_CREATE, subscriptions.get(0).getBundleId()); - } - } - } - - private void validateInputSubscriptionsKnown(final List subscriptions, final List input) - throws SubscriptionBaseRepairException { - for (final SubscriptionBaseTimeline cur : input) { - boolean found = false; - for (final SubscriptionBase s : subscriptions) { - if (s.getId().equals(cur.getId())) { - found = true; - break; - } - } - if (!found) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_UNKNOWN_SUBSCRIPTION, cur.getId()); - } - } - } - - private void validateFirstNewEvent(final DefaultSubscriptionBase data, final NewEvent firstNewEvent, final DateTime lastBPRemainingTime, final DateTime lastRemainingTime) - throws SubscriptionBaseRepairException { - if (lastBPRemainingTime != null && - firstNewEvent.getRequestedDate().isBefore(lastBPRemainingTime)) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING, firstNewEvent.getSubscriptionTransitionType(), data.getId()); - } - if (lastRemainingTime != null && - firstNewEvent.getRequestedDate().isBefore(lastRemainingTime)) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING, firstNewEvent.getSubscriptionTransitionType(), data.getId()); - } - - } - - private Collection createOrderedNewEventInput(final List subscriptionsReapir) { - final TreeSet newEventSet = new TreeSet(new Comparator() { - @Override - public int compare(final NewEvent o1, final NewEvent o2) { - return o1.getRequestedDate().compareTo(o2.getRequestedDate()); - } - }); - for (final SubscriptionBaseTimeline cur : subscriptionsReapir) { - for (final NewEvent e : cur.getNewEvents()) { - newEventSet.add(new DefaultNewEvent(cur.getId(), e.getPlanPhaseSpecifier(), e.getRequestedDate(), e.getSubscriptionTransitionType())); - } - } - - return newEventSet; - } - - private List getRemainingEventsAndValidateDeletedEvents(final SubscriptionDataRepair data, final DateTime firstBPDeletedTime, - final List deletedEvents) - throws SubscriptionBaseRepairException { - if (deletedEvents == null || deletedEvents.size() == 0) { - return data.getEvents(); - } - - int nbDeleted = 0; - final LinkedList result = new LinkedList(); - for (final SubscriptionBaseEvent cur : data.getEvents()) { - - boolean foundDeletedEvent = false; - for (final SubscriptionBaseTimeline.DeletedEvent d : deletedEvents) { - if (cur.getId().equals(d.getEventId())) { - foundDeletedEvent = true; - nbDeleted++; - break; - } - } - if (!foundDeletedEvent && nbDeleted > 0) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_INVALID_DELETE_SET, cur.getId(), data.getId()); - } - if (firstBPDeletedTime != null && - !cur.getEffectiveDate().isBefore(firstBPDeletedTime) && - !foundDeletedEvent) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_MISSING_AO_DELETE_EVENT, cur.getId(), data.getId()); - } - - if (nbDeleted == 0) { - result.add(cur); - } - } - - if (nbDeleted != deletedEvents.size()) { - for (final SubscriptionBaseTimeline.DeletedEvent d : deletedEvents) { - boolean found = false; - for (final SubscriptionBaseTransition cur : data.getAllTransitions()) { - if (((SubscriptionBaseTransitionData) cur).getId().equals(d.getEventId())) { - found = true; - } - } - if (!found) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_NON_EXISTENT_DELETE_EVENT, d.getEventId(), data.getId()); - } - } - - } - - return result; - } - - private String getViewId(final DateTime lastUpdateBundleDate, final List subscriptions) { + private String getViewId(final DateTime lastUpdateBundleDate, final List subscriptions) { final StringBuilder tmp = new StringBuilder(); long lastOrderedId = -1; for (final SubscriptionBase cur : subscriptions) { @@ -445,7 +128,7 @@ public String getExternalKey() { }; } - private List createGetSubscriptionRepairList(final List subscriptions, final List inRepair, final InternalTenantContext tenantContext) throws CatalogApiException { + private List createGetSubscriptionRepairList(final List subscriptions, final List inRepair, final InternalTenantContext tenantContext) throws CatalogApiException { final List result = new LinkedList(); final Set repairIds = new TreeSet(); @@ -456,62 +139,11 @@ private List createGetSubscriptionRepairList(final Lis for (final SubscriptionBase cur : subscriptions) { if (!repairIds.contains(cur.getId())) { - result.add(new DefaultSubscriptionBaseTimeline((SubscriptionDataRepair) cur, catalogService.getFullCatalog(tenantContext))); + result.add(new DefaultSubscriptionBaseTimeline((DefaultSubscriptionBase) cur, catalogService.getFullCatalog(tenantContext))); } } - - return result; - } - - private List convertDataRepair(final List input, final InternalTenantContext tenantContext) throws CatalogApiException { - final List result = new LinkedList(); - for (final SubscriptionDataRepair cur : input) { - result.add(new DefaultSubscriptionBaseTimeline(cur, catalogService.getFullCatalog(tenantContext))); - } - return result; } - private SubscriptionDataRepair findSubscriptionDataRepair(final UUID targetId, final List input) { - for (final SubscriptionDataRepair cur : input) { - if (cur.getId().equals(targetId)) { - return cur; - } - } - - return null; - } - - private SubscriptionDataRepair createSubscriptionDataRepair(final DefaultSubscriptionBase curData, final DateTime newBundleStartDate, final DateTime newSubscriptionStartDate, - final List initialEvents, final InternalTenantContext tenantContext) throws CatalogApiException { - final SubscriptionBuilder builder = new SubscriptionBuilder(curData); - builder.setActiveVersion(curData.getActiveVersion() + 1); - if (newBundleStartDate != null) { - builder.setBundleStartDate(newBundleStartDate); - } - if (newSubscriptionStartDate != null) { - builder.setAlignStartDate(newSubscriptionStartDate); - } - if (initialEvents.size() > 0) { - for (final SubscriptionBaseEvent cur : initialEvents) { - cur.setActiveVersion(builder.getActiveVersion()); - } - } - - final SubscriptionDataRepair subscriptiondataRepair = new SubscriptionDataRepair(builder, curData.getEvents(), repairApiService, (SubscriptionDao) repairDao, clock, addonUtils, catalogService, internalCallContextFactory); - final Catalog fullCatalog = catalogService.getFullCatalog(tenantContext); - subscriptiondataRepair.rebuildTransitions(curData.getEvents(), fullCatalog); - return subscriptiondataRepair; - } - - private SubscriptionBaseTimeline findAndCreateSubscriptionRepair(final UUID target, final List input) { - for (final SubscriptionBaseTimeline cur : input) { - if (target.equals(cur.getId())) { - return new DefaultSubscriptionBaseTimeline(cur); - } - } - - return null; - } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/RepairSubscriptionApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/RepairSubscriptionApiService.java deleted file mode 100644 index 6a0efd432a..0000000000 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/RepairSubscriptionApiService.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.UUID; - -import org.joda.time.DateTime; -import org.killbill.billing.catalog.api.CatalogService; -import org.killbill.billing.catalog.api.Product; -import org.killbill.billing.subscription.alignment.PlanAligner; -import org.killbill.billing.subscription.api.SubscriptionBaseApiService; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseApiService; -import org.killbill.billing.subscription.engine.addon.AddonUtils; -import org.killbill.billing.subscription.engine.dao.SubscriptionDao; -import org.killbill.billing.subscription.glue.DefaultSubscriptionModule; -import org.killbill.billing.util.callcontext.CallContext; -import org.killbill.billing.util.callcontext.InternalCallContextFactory; -import org.killbill.clock.Clock; - -import com.google.inject.Inject; -import com.google.inject.name.Named; - -public class RepairSubscriptionApiService extends DefaultSubscriptionBaseApiService implements SubscriptionBaseApiService { - - @Inject - public RepairSubscriptionApiService(final Clock clock, - @Named(DefaultSubscriptionModule.REPAIR_NAMED) final SubscriptionDao dao, - final CatalogService catalogService, - final PlanAligner planAligner, - final AddonUtils addonUtils, - final InternalCallContextFactory internalCallContextFactory) { - super(clock, dao, catalogService, planAligner, addonUtils, internalCallContextFactory); - } - - // Nothing to do for repair as we pass all the repair events in the stream - @Override - public int cancelAddOnsIfRequired(final Product baseProduct, final UUID bundleId, final DateTime effectiveDate, final CallContext context) { - return 0; - } -} diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/RepairSubscriptionLifecycleDao.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/RepairSubscriptionLifecycleDao.java deleted file mode 100644 index dac507f889..0000000000 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/RepairSubscriptionLifecycleDao.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.List; -import java.util.UUID; - -import org.killbill.billing.subscription.events.SubscriptionBaseEvent; -import org.killbill.billing.callcontext.InternalTenantContext; - -public interface RepairSubscriptionLifecycleDao { - - public void initializeRepair(UUID subscriptionId, List initialEvents, InternalTenantContext context); - - public void cleanup(InternalTenantContext context); -} diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionDataRepair.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionDataRepair.java deleted file mode 100644 index 86ea5a5f16..0000000000 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionDataRepair.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import javax.annotation.concurrent.Immutable; - -import org.joda.time.DateTime; - -import org.killbill.billing.ErrorCode; -import org.killbill.billing.ObjectType; -import org.killbill.billing.callcontext.InternalCallContext; -import org.killbill.billing.callcontext.InternalTenantContext; -import org.killbill.billing.catalog.api.Catalog; -import org.killbill.billing.catalog.api.CatalogApiException; -import org.killbill.billing.catalog.api.CatalogService; -import org.killbill.billing.catalog.api.Plan; -import org.killbill.billing.catalog.api.PlanPhasePriceOverride; -import org.killbill.billing.catalog.api.PlanPhaseSpecifier; -import org.killbill.billing.catalog.api.Product; -import org.killbill.billing.catalog.api.ProductCategory; -import org.killbill.billing.entitlement.api.Entitlement.EntitlementState; -import org.killbill.billing.subscription.api.SubscriptionBaseApiService; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException; -import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition; -import org.killbill.billing.subscription.api.user.SubscriptionBuilder; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; -import org.killbill.billing.subscription.engine.addon.AddonUtils; -import org.killbill.billing.subscription.engine.dao.SubscriptionDao; -import org.killbill.billing.subscription.events.SubscriptionBaseEvent; -import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType; -import org.killbill.billing.subscription.events.user.ApiEventBuilder; -import org.killbill.billing.subscription.events.user.ApiEventCancel; -import org.killbill.billing.util.callcontext.CallContext; -import org.killbill.billing.util.callcontext.InternalCallContextFactory; -import org.killbill.clock.Clock; - -import com.google.common.base.Predicate; -import com.google.common.collect.Collections2; -import com.google.common.collect.ImmutableList; - -public class SubscriptionDataRepair extends DefaultSubscriptionBase { - - private final AddonUtils addonUtils; - private final Clock clock; - private final SubscriptionDao repairDao; - private final CatalogService catalogService; - private final List initialEvents; - private final InternalCallContextFactory internalCallContextFactory; - - - public SubscriptionDataRepair(final SubscriptionBuilder builder, final List initialEvents, final SubscriptionBaseApiService apiService, - final SubscriptionDao dao, final Clock clock, final AddonUtils addonUtils, final CatalogService catalogService, - final InternalCallContextFactory internalCallContextFactory) { - super(builder, apiService, clock); - this.repairDao = dao; - this.addonUtils = addonUtils; - this.clock = clock; - this.catalogService = catalogService; - this.initialEvents = initialEvents; - this.internalCallContextFactory = internalCallContextFactory; - } - - - - public SubscriptionDataRepair(final DefaultSubscriptionBase defaultSubscriptionBase, final SubscriptionBaseApiService apiService, - final SubscriptionDao dao, final Clock clock, final AddonUtils addonUtils, final CatalogService catalogService, - final InternalCallContextFactory internalCallContextFactory) { - super(defaultSubscriptionBase, apiService , clock); - this.repairDao = dao; - this.addonUtils = addonUtils; - this.clock = clock; - this.catalogService = catalogService; - this.initialEvents = defaultSubscriptionBase.getEvents(); - this.internalCallContextFactory = internalCallContextFactory; - } - - DateTime getLastUserEventEffectiveDate() { - DateTime res = null; - for (final SubscriptionBaseEvent cur : events) { - if (cur.getActiveVersion() != getActiveVersion()) { - break; - } - if (cur.getType() == EventType.PHASE) { - continue; - } - res = cur.getEffectiveDate(); - } - return res; - } - - public void addNewRepairEvent(final DefaultNewEvent input, final SubscriptionDataRepair baseSubscription, final List addonSubscriptions, final CallContext context) - throws SubscriptionBaseRepairException { - - try { - - final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(baseSubscription.getId(), ObjectType.SUBSCRIPTION, context); - - final PlanPhaseSpecifier spec = input.getPlanPhaseSpecifier(); - switch (input.getSubscriptionTransitionType()) { - case CREATE: - case RE_CREATE: - recreate(spec, ImmutableList.of(), input.getRequestedDate(), context); - checkAddonRights(baseSubscription, internalTenantContext); - break; - case CHANGE: - changePlanWithDate(spec.getProductName(), spec.getBillingPeriod(), spec.getPriceListName(), ImmutableList.of(), input.getRequestedDate(), context); - checkAddonRights(baseSubscription, internalTenantContext); - trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context); - break; - case CANCEL: - cancelWithDate(input.getRequestedDate(), context); - trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context); - break; - case PHASE: - break; - default: - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_UNKNOWN_TYPE, input.getSubscriptionTransitionType(), id); - } - } catch (SubscriptionBaseApiException e) { - throw new SubscriptionBaseRepairException(e); - } catch (CatalogApiException e) { - throw new SubscriptionBaseRepairException(e); - } - } - - public void addFutureAddonCancellation(final List addOnSubscriptionInRepair, final CallContext context) throws CatalogApiException { - - if (getCategory() != ProductCategory.BASE) { - return; - } - - final SubscriptionBaseTransition pendingTransition = getPendingTransition(); - if (pendingTransition == null) { - return; - } - final Product baseProduct = (pendingTransition.getTransitionType() == SubscriptionBaseTransitionType.CANCEL) ? null : - pendingTransition.getNextPlan().getProduct(); - - addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, pendingTransition.getEffectiveTransitionTime(), context); - } - - private void trickleDownBPEffectForAddon(final List addOnSubscriptionInRepair, final DateTime effectiveDate, final CallContext context) - throws SubscriptionBaseApiException, CatalogApiException { - - if (getCategory() != ProductCategory.BASE) { - return; - } - - final Product baseProduct = (getState() == EntitlementState.CANCELLED) ? - null : getCurrentPlan().getProduct(); - addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, effectiveDate, context); - } - - private void addAddonCancellationIfRequired(final List addOnSubscriptionInRepair, final Product baseProduct, - final DateTime effectiveDate, final CallContext context) throws CatalogApiException { - - final DateTime now = clock.getUTCNow(); - final Iterator it = addOnSubscriptionInRepair.iterator(); - while (it.hasNext()) { - final SubscriptionDataRepair cur = it.next(); - if (cur.getState() == EntitlementState.CANCELLED || - cur.getCategory() != ProductCategory.ADD_ON) { - continue; - } - final Plan addonCurrentPlan = cur.getCurrentPlan(); - final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(cur.getId(), ObjectType.SUBSCRIPTION, context); - - if (baseProduct == null || - addonUtils.isAddonIncludedFromProdName(baseProduct.getName(), addonCurrentPlan, effectiveDate, internalCallContext) || - !addonUtils.isAddonAvailableFromProdName(baseProduct.getName(), addonCurrentPlan, effectiveDate, internalCallContext)) { - - final SubscriptionBaseEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder() - .setSubscriptionId(cur.getId()) - .setActiveVersion(cur.getActiveVersion()) - .setProcessedDate(now) - .setEffectiveDate(effectiveDate) - .setRequestedDate(now) - .setFromDisk(true)); - repairDao.cancelSubscription(cur, cancelEvent, internalCallContext, 0); - final Catalog fullCatalog = catalogService.getFullCatalog(internalCallContext); - cur.rebuildTransitions(repairDao.getEventsForSubscription(cur.getId(), internalCallContextFactory.createInternalTenantContext(context)), fullCatalog); - } - } - } - - private void checkAddonRights(final SubscriptionDataRepair baseSubscription, final InternalTenantContext internalTenantContext) - throws SubscriptionBaseApiException, CatalogApiException { - if (getCategory() == ProductCategory.ADD_ON) { - addonUtils.checkAddonCreationRights(baseSubscription, getCurrentPlan(), clock.getUTCNow(), internalTenantContext); - } - } - - public List getEvents() { - return events; - } - - public List getInitialEvents() { - return initialEvents; - } - - public Collection getNewEvents() { - return Collections2.filter(events, new Predicate() { - @Override - public boolean apply(final SubscriptionBaseEvent input) { - return !initialEvents.contains(input); - } - }); - } -} diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java index 03e813e600..7c9158972f 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java @@ -46,14 +46,11 @@ import org.killbill.billing.entitlement.api.SubscriptionApiException; import org.killbill.billing.entity.EntityPersistenceException; import org.killbill.billing.events.EffectiveSubscriptionInternalEvent; -import org.killbill.billing.events.RepairSubscriptionInternalEvent; import org.killbill.billing.subscription.api.SubscriptionBase; import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; import org.killbill.billing.subscription.api.migration.AccountMigrationData; import org.killbill.billing.subscription.api.migration.AccountMigrationData.BundleMigrationData; import org.killbill.billing.subscription.api.migration.AccountMigrationData.SubscriptionMigrationData; -import org.killbill.billing.subscription.api.timeline.DefaultRepairSubscriptionEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionDataRepair; import org.killbill.billing.subscription.api.transfer.TransferCancelData; import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent; import org.killbill.billing.subscription.api.user.DefaultRequestedSubscriptionEvent; @@ -970,44 +967,6 @@ public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFa }); } - @Override - public void repair(final UUID accountId, final UUID bundleId, final List inRepair, final InternalCallContext context) { - transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() { - @Override - public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception { - final SubscriptionSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class); - - final SubscriptionEventSqlDao transEventDao = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class); - for (final SubscriptionDataRepair cur : inRepair) { - transactional.updateForRepair(cur.getId().toString(), cur.getActiveVersion(), cur.getAlignStartDate().toDate(), cur.getBundleStartDate().toDate(), context); - for (final SubscriptionBaseEvent event : cur.getInitialEvents()) { - transEventDao.updateVersion(event.getId().toString(), event.getActiveVersion(), context); - } - for (final SubscriptionBaseEvent event : cur.getNewEvents()) { - transEventDao.create(new SubscriptionEventModelDao(event), context); - if (event.getEffectiveDate().isAfter(clock.getUTCNow())) { - recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory, - event.getEffectiveDate(), - new SubscriptionNotificationKey(event.getId()), - context); - } - } - } - - try { - // Note: we don't send a requested change event here, but a repair event - final RepairSubscriptionInternalEvent busEvent = new DefaultRepairSubscriptionEvent(accountId, bundleId, clock.getUTCNow(), - context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()); - eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getHandle().getConnection()); - } catch (EventBusException e) { - log.warn("Failed to post repair subscription event for bundle " + bundleId, e); - } - - return null; - } - }); - } - @Override public void transfer(final UUID srcAccountId, final UUID destAccountId, final BundleMigrationData bundleTransferData, final List transferCancelData, final InternalCallContext fromContext, final InternalCallContext toContext) { diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java deleted file mode 100644 index 622513460d..0000000000 --- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.engine.dao; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; - -import javax.inject.Inject; - -import org.skife.jdbi.v2.IDBI; - -import org.killbill.billing.ErrorCode; -import org.killbill.billing.callcontext.InternalCallContext; -import org.killbill.billing.callcontext.InternalTenantContext; -import org.killbill.clock.Clock; -import org.killbill.billing.entitlement.api.SubscriptionApiException; -import org.killbill.billing.subscription.api.SubscriptionBase; -import org.killbill.billing.subscription.api.migration.AccountMigrationData; -import org.killbill.billing.subscription.api.migration.AccountMigrationData.BundleMigrationData; -import org.killbill.billing.subscription.api.timeline.RepairSubscriptionLifecycleDao; -import org.killbill.billing.subscription.api.timeline.SubscriptionDataRepair; -import org.killbill.billing.subscription.api.transfer.TransferCancelData; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle; -import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle; -import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao; -import org.killbill.billing.subscription.events.SubscriptionBaseEvent; -import org.killbill.billing.subscription.exceptions.SubscriptionBaseError; -import org.killbill.billing.util.cache.CacheControllerDispatcher; -import org.killbill.billing.util.dao.NonEntityDao; -import org.killbill.billing.util.entity.Pagination; -import org.killbill.billing.util.entity.dao.EntityDaoBase; -import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper; - -import com.google.common.base.Function; -import com.google.common.collect.Collections2; - -public class RepairSubscriptionDao extends EntityDaoBase implements SubscriptionDao, RepairSubscriptionLifecycleDao { - - private static final String NOT_IMPLEMENTED = "Not implemented"; - - private final ThreadLocal> preThreadsInRepairSubscriptions = new ThreadLocal>(); - - @Inject - public RepairSubscriptionDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) { - super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao), BundleSqlDao.class); - } - - @Override - protected SubscriptionApiException generateAlreadyExistsException(final SubscriptionBundleModelDao entity, final InternalCallContext context) { - return new SubscriptionApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, entity.getExternalKey()); - } - - private static final class SubscriptionEventWithOrderingId { - - private final SubscriptionBaseEvent event; - private final long orderingId; - - public SubscriptionEventWithOrderingId(final SubscriptionBaseEvent event, final long orderingId) { - this.event = event; - this.orderingId = orderingId; - } - - public SubscriptionBaseEvent getEvent() { - return event; - } - - public long getOrderingId() { - return orderingId; - } - - @Override - public String toString() { - final StringBuilder tmp = new StringBuilder(); - tmp.append("["); - tmp.append(event.getType()); - tmp.append(": effDate="); - tmp.append(event.getEffectiveDate()); - tmp.append(", subId="); - tmp.append(event.getSubscriptionId()); - tmp.append(", ordering="); - tmp.append(event.getTotalOrdering()); - tmp.append("]"); - return tmp.toString(); - } - } - - private static final class SubscriptionRepairEvent { - - private final Set events; - private long curOrderingId; - - public SubscriptionRepairEvent(final List initialEvents) { - this.events = new TreeSet(new Comparator() { - @Override - public int compare(final SubscriptionEventWithOrderingId o1, final SubscriptionEventWithOrderingId o2) { - // Work around jdk7 change: compare(o1, o1) is now invoked when inserting the first element - // See: - // - http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5045147 - // - http://hg.openjdk.java.net/jdk7/tl/jdk/rev/bf37edb38fbb - if (o1 == o2) { - return 0; - } - - final int result = o1.getEvent().getEffectiveDate().compareTo(o2.getEvent().getEffectiveDate()); - if (result == 0) { - if (o1.getOrderingId() < o2.getOrderingId()) { - return -1; - } else if (o1.getOrderingId() > o2.getOrderingId()) { - return 1; - } else { - throw new RuntimeException(String.format(" Repair subscription events should not have the same orderingId %s, %s ", o1, o2)); - } - } - return result; - } - }); - - this.curOrderingId = 0; - - if (initialEvents != null) { - addEvents(initialEvents); - } - } - - public List getEvents() { - return new ArrayList(Collections2.transform(events, new Function() { - @Override - public SubscriptionBaseEvent apply(SubscriptionEventWithOrderingId in) { - return in.getEvent(); - } - })); - } - - public void addEvents(final List newEvents) { - for (final SubscriptionBaseEvent cur : newEvents) { - events.add(new SubscriptionEventWithOrderingId(cur, curOrderingId++)); - } - } - } - - private Map getRepairMap() { - if (preThreadsInRepairSubscriptions.get() == null) { - preThreadsInRepairSubscriptions.set(new HashMap()); - } - return preThreadsInRepairSubscriptions.get(); - } - - private SubscriptionRepairEvent getRepairSubscriptionEvents(final UUID subscriptionId) { - final Map map = getRepairMap(); - return map.get(subscriptionId); - } - - @Override - public List getEventsForSubscription(final UUID subscriptionId, final InternalTenantContext context) { - final SubscriptionRepairEvent target = getRepairSubscriptionEvents(subscriptionId); - return new LinkedList(target.getEvents()); - } - - @Override - public void createSubscription(final DefaultSubscriptionBase subscription, final List createEvents, final InternalCallContext context) { - addEvents(subscription.getId(), createEvents); - } - - @Override - public void recreateSubscription(final DefaultSubscriptionBase subscription, final List recreateEvents, final InternalCallContext context) { - addEvents(subscription.getId(), recreateEvents); - } - - @Override - public void cancelSubscription(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent cancelEvent, final InternalCallContext context, final int cancelSeq) { - final UUID subscriptionId = subscription.getId(); - final long activeVersion = cancelEvent.getActiveVersion(); - addEvents(subscriptionId, Collections.singletonList(cancelEvent)); - final SubscriptionRepairEvent target = getRepairSubscriptionEvents(subscriptionId); - boolean foundCancelEvent = false; - for (final SubscriptionBaseEvent cur : target.getEvents()) { - if (cur.getId().equals(cancelEvent.getId())) { - foundCancelEvent = true; - } else if (foundCancelEvent) { - cur.setActiveVersion(activeVersion - 1); - } - } - } - - @Override - public void cancelSubscriptions(final List subscriptions, final List cancelEvents, final InternalCallContext context) { - } - - @Override - public void changePlan(final DefaultSubscriptionBase subscription, final List changeEvents, final InternalCallContext context) { - addEvents(subscription.getId(), changeEvents); - } - - @Override - public void initializeRepair(final UUID subscriptionId, final List initialEvents, final InternalTenantContext context) { - final Map map = getRepairMap(); - if (map.get(subscriptionId) == null) { - final SubscriptionRepairEvent value = new SubscriptionRepairEvent(initialEvents); - map.put(subscriptionId, value); - } else { - throw new SubscriptionBaseError(String.format("Unexpected SubscriptionRepairEvent %s for thread %s", subscriptionId, Thread.currentThread().getName())); - } - } - - @Override - public void cleanup(final InternalTenantContext context) { - final Map map = getRepairMap(); - map.clear(); - } - - private void addEvents(final UUID subscriptionId, final List events) { - final SubscriptionRepairEvent target = getRepairSubscriptionEvents(subscriptionId); - target.addEvents(events); - } - - @Override - public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List uncancelEvents, final InternalCallContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public List getSubscriptionBundleForAccount(final UUID accountId, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public SubscriptionBaseBundle getSubscriptionBundleFromId(final UUID bundleId, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public SubscriptionBaseBundle createSubscriptionBundle(final DefaultSubscriptionBaseBundle bundle, final InternalCallContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public SubscriptionBase getSubscriptionFromId(final UUID subscriptionId, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public SubscriptionBase getBaseSubscription(final UUID bundleId, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public List getSubscriptions(final UUID bundleId, final List dryRunEvents, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public void updateChargedThroughDate(final DefaultSubscriptionBase subscription, final InternalCallContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public void createNextPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent nextPhase, final InternalCallContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public SubscriptionBaseEvent getEventById(final UUID eventId, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public Map> getSubscriptionsForAccount(final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public Iterable getFutureEventsForAccount(final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public List getPendingEventsForSubscription(final UUID subscriptionId, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public void migrate(final UUID accountId, final AccountMigrationData data, final InternalCallContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public void repair(final UUID accountId, final UUID bundleId, final List inRepair, final InternalCallContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public void transfer(final UUID srcAccountId, final UUID destAccountId, final BundleMigrationData data, - final List transferCancelData, final InternalCallContext fromContext, - final InternalCallContext toContext) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public void updateBundleExternalKey(final UUID bundleId, final String externalKey, final InternalCallContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public List getSubscriptionBundlesForKey(final String bundleKey, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public Pagination searchSubscriptionBundles(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public List getNonAOSubscriptionIdsForKey(final String bundleKey, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } - - @Override - public List getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) { - throw new SubscriptionBaseError(NOT_IMPLEMENTED); - } -} diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java index 5503b991af..13cfa2b7b3 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java @@ -27,7 +27,6 @@ import org.killbill.billing.subscription.api.SubscriptionBase; import org.killbill.billing.subscription.api.migration.AccountMigrationData; import org.killbill.billing.subscription.api.migration.AccountMigrationData.BundleMigrationData; -import org.killbill.billing.subscription.api.timeline.SubscriptionDataRepair; import org.killbill.billing.subscription.api.transfer.TransferCancelData; import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle; @@ -99,8 +98,5 @@ public interface SubscriptionDao extends EntityDao inRepair, InternalCallContext context); - } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java b/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java index 01531ea727..e0ba5ab3e3 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java @@ -29,8 +29,6 @@ import org.killbill.billing.subscription.api.migration.SubscriptionBaseMigrationApi; import org.killbill.billing.subscription.api.svcs.DefaultSubscriptionInternalApi; import org.killbill.billing.subscription.api.timeline.DefaultSubscriptionBaseTimelineApi; -import org.killbill.billing.subscription.api.timeline.RepairSubscriptionApiService; -import org.killbill.billing.subscription.api.timeline.RepairSubscriptionLifecycleDao; import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimelineApi; import org.killbill.billing.subscription.api.transfer.DefaultSubscriptionBaseTransferApi; import org.killbill.billing.subscription.api.transfer.SubscriptionBaseTransferApi; @@ -38,18 +36,13 @@ import org.killbill.billing.subscription.engine.addon.AddonUtils; import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService; import org.killbill.billing.subscription.engine.dao.DefaultSubscriptionDao; -import org.killbill.billing.subscription.engine.dao.RepairSubscriptionDao; import org.killbill.billing.subscription.engine.dao.SubscriptionDao; import org.killbill.billing.util.config.SubscriptionConfig; import org.killbill.billing.util.glue.KillBillModule; import org.skife.config.ConfigurationObjectFactory; -import com.google.inject.name.Names; - public class DefaultSubscriptionModule extends KillBillModule implements SubscriptionModule { - public static final String REPAIR_NAMED = "repair"; - public DefaultSubscriptionModule(final KillbillConfigSource configSource) { super(configSource); } @@ -61,15 +54,10 @@ protected void installConfig() { protected void installSubscriptionDao() { bind(SubscriptionDao.class).to(DefaultSubscriptionDao.class).asEagerSingleton(); - bind(SubscriptionDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionDao.class); - bind(RepairSubscriptionLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionDao.class); - bind(RepairSubscriptionDao.class).asEagerSingleton(); } protected void installSubscriptionCore() { - bind(SubscriptionBaseApiService.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionApiService.class).asEagerSingleton(); bind(SubscriptionBaseApiService.class).to(DefaultSubscriptionBaseApiService.class).asEagerSingleton(); - bind(DefaultSubscriptionBaseService.class).asEagerSingleton(); bind(PlanAligner.class).asEagerSingleton(); bind(AddonUtils.class).asEagerSingleton(); diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java b/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java index 2121dc0e34..11417821f0 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java @@ -24,10 +24,8 @@ import org.killbill.billing.GuicyKillbillTestSuiteNoDB; import org.killbill.billing.entitlement.api.Entitlement.EntitlementState; -import org.killbill.billing.subscription.api.timeline.DefaultRepairSubscriptionEvent; import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent; import org.killbill.billing.events.EffectiveSubscriptionInternalEvent; -import org.killbill.billing.events.RepairSubscriptionInternalEvent; import org.killbill.billing.util.jackson.ObjectMapper; public class TestEventJson extends GuicyKillbillTestSuiteNoDB { @@ -47,15 +45,4 @@ public void testSubscriptionEvent() throws Exception { final Object obj = mapper.readValue(json, claz); Assert.assertTrue(obj.equals(e)); } - - @Test(groups = "fast") - public void testRepairSubscriptionEvent() throws Exception { - final RepairSubscriptionInternalEvent e = new DefaultRepairSubscriptionEvent(UUID.randomUUID(), UUID.randomUUID(), new DateTime(), 1L, 2L, null); - - final String json = mapper.writeValueAsString(e); - - final Class claz = Class.forName(DefaultRepairSubscriptionEvent.class.getName()); - final Object obj = mapper.readValue(json, claz); - Assert.assertTrue(obj.equals(e)); - } } diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairBP.java b/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairBP.java deleted file mode 100644 index 49601b604e..0000000000 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairBP.java +++ /dev/null @@ -1,702 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -import org.joda.time.DateTime; -import org.joda.time.Interval; -import org.testng.annotations.Test; - -import org.killbill.billing.ErrorCode; -import org.killbill.billing.api.TestApiListener.NextEvent; -import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.catalog.api.PhaseType; -import org.killbill.billing.catalog.api.Plan; -import org.killbill.billing.catalog.api.PlanPhase; -import org.killbill.billing.catalog.api.PlanPhaseSpecifier; -import org.killbill.billing.catalog.api.PriceListSet; -import org.killbill.billing.catalog.api.ProductCategory; -import org.killbill.billing.entitlement.api.Entitlement.EntitlementState; -import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB; -import org.killbill.billing.subscription.api.SubscriptionBase; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.DeletedEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.ExistingEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.NewEvent; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; -import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException; -import org.killbill.billing.subscription.api.user.SubscriptionEvents; -import org.killbill.billing.subscription.api.user.TestSubscriptionHelper.TestWithException; -import org.killbill.billing.subscription.api.user.TestSubscriptionHelper.TestWithExceptionCallback; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; - -public class TestRepairBP extends SubscriptionTestSuiteWithEmbeddedDB { - - @Test(groups = "slow") - public void testFetchBundleRepair() throws Exception { - final String baseProduct = "Shotgun"; - final BillingPeriod baseTerm = BillingPeriod.MONTHLY; - final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME; - - // CREATE BP - final SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList); - - final String aoProduct = "Telescopic-Scope"; - final BillingPeriod aoTerm = BillingPeriod.MONTHLY; - final String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME; - - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, aoProduct, aoTerm, aoPriceList); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - final List subscriptionRepair = bundleRepair.getSubscriptions(); - assertEquals(subscriptionRepair.size(), 2); - - for (final SubscriptionBaseTimeline cur : subscriptionRepair) { - assertNull(cur.getDeletedEvents()); - assertNull(cur.getNewEvents()); - - final List events = cur.getExistingEvents(); - assertEquals(events.size(), 2); - testUtil.sortExistingEvent(events); - - assertEquals(events.get(0).getSubscriptionTransitionType(), SubscriptionBaseTransitionType.CREATE); - assertEquals(events.get(1).getSubscriptionTransitionType(), SubscriptionBaseTransitionType.PHASE); - final boolean isBP = cur.getId().equals(baseSubscription.getId()); - if (isBP) { - assertEquals(cur.getId(), baseSubscription.getId()); - - assertEquals(events.get(0).getPlanPhaseSpecifier().getProductName(), baseProduct); - assertEquals(events.get(0).getPlanPhaseSpecifier().getPhaseType(), PhaseType.TRIAL); - assertEquals(events.get(0).getPlanPhaseSpecifier().getProductCategory(), ProductCategory.BASE); - assertEquals(events.get(0).getPlanPhaseSpecifier().getPriceListName(), basePriceList); - assertEquals(events.get(0).getPlanPhaseSpecifier().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD); - - assertEquals(events.get(1).getPlanPhaseSpecifier().getProductName(), baseProduct); - assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN); - assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(), ProductCategory.BASE); - assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), basePriceList); - assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), baseTerm); - } else { - assertEquals(cur.getId(), aoSubscription.getId()); - - assertEquals(events.get(0).getPlanPhaseSpecifier().getProductName(), aoProduct); - assertEquals(events.get(0).getPlanPhaseSpecifier().getPhaseType(), PhaseType.DISCOUNT); - assertEquals(events.get(0).getPlanPhaseSpecifier().getProductCategory(), ProductCategory.ADD_ON); - assertEquals(events.get(0).getPlanPhaseSpecifier().getPriceListName(), aoPriceList); - assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm); - - assertEquals(events.get(1).getPlanPhaseSpecifier().getProductName(), aoProduct); - assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN); - assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(), ProductCategory.ADD_ON); - assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), aoPriceList); - assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm); - } - } - assertListenerStatus(); - } - - @Test(groups = "slow") - public void testBPRepairWithCancellationOnstart() throws Exception { - final String baseProduct = "Shotgun"; - final DateTime startDate = clock.getUTCNow(); - - // CREATE BP - final SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate); - - // Stays in trial-- for instance - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(10)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CANCEL, baseSubscription.getStartDate(), null); - - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - // FIRST ISSUE DRY RUN - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - testUtil.sortEventsOnBundle(dryRunBundleRepair); - List subscriptionRepair = dryRunBundleRepair.getSubscriptions(); - assertEquals(subscriptionRepair.size(), 1); - SubscriptionBaseTimeline cur = subscriptionRepair.get(0); - int index = 0; - final List events = subscriptionRepair.get(0).getExistingEvents(); - assertEquals(events.size(), 2); - final List expected = new LinkedList(); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, baseProduct, PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate())); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CANCEL, baseProduct, PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate())); - - for (final ExistingEvent e : expected) { - testUtil.validateExistingEventForAssertion(e, events.get(index++)); - } - - final DefaultSubscriptionBase dryRunBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - - assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId()); - assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate()); - - final Plan currentPlan = dryRunBaseSubscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), baseProduct); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - final PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase(); - assertNotNull(currentPhase); - assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); - - // SECOND RE-ISSUE CALL-- NON DRY RUN - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - assertListenerStatus(); - - subscriptionRepair = realRunBundleRepair.getSubscriptions(); - assertEquals(subscriptionRepair.size(), 1); - cur = subscriptionRepair.get(0); - assertEquals(cur.getId(), baseSubscription.getId()); - index = 0; - for (final ExistingEvent e : expected) { - testUtil.validateExistingEventForAssertion(e, events.get(index++)); - } - final DefaultSubscriptionBase realRunBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(realRunBaseSubscription.getAllTransitions().size(), 2); - - assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId()); - assertEquals(realRunBaseSubscription.getStartDate(), startDate); - - assertEquals(realRunBaseSubscription.getState(), EntitlementState.CANCELLED); - - assertListenerStatus(); - } - - @Test(groups = "slow") - public void testBPRepairReplaceCreateBeforeTrial() throws Exception { - final String baseProduct = "Shotgun"; - final String newBaseProduct = "Assault-Rifle"; - - final DateTime startDate = clock.getUTCNow(); - final int clockShift = -1; - final DateTime restartDate = startDate.plusDays(clockShift).minusDays(1); - final LinkedList expected = new LinkedList(); - - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate)); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30))); - - testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected); - assertListenerStatus(); - } - - @Test(groups = "slow") - public void testBPRepairReplaceCreateInTrial() throws Exception { - final String baseProduct = "Shotgun"; - final String newBaseProduct = "Assault-Rifle"; - - final DateTime startDate = clock.getUTCNow(); - final int clockShift = 10; - final DateTime restartDate = startDate.plusDays(clockShift).minusDays(1); - final LinkedList expected = new LinkedList(); - - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate)); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30))); - - final UUID baseSubscriptionId = testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected); - - testListener.pushExpectedEvent(NextEvent.PHASE); - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - - // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN - final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscriptionId, internalCallContext); - - assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - assertEquals(subscription.getBundleId(), bundle.getId()); - assertEquals(subscription.getStartDate(), restartDate); - assertEquals(subscription.getBundleStartDate(), restartDate); - - final Plan currentPlan = subscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), newBaseProduct); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - final PlanPhase currentPhase = subscription.getCurrentPhase(); - assertNotNull(currentPhase); - assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); - - assertListenerStatus(); - } - - @Test(groups = "slow") - public void testBPRepairReplaceCreateAfterTrial() throws Exception { - final String baseProduct = "Shotgun"; - final String newBaseProduct = "Assault-Rifle"; - - final DateTime startDate = clock.getUTCNow(); - final int clockShift = 40; - final DateTime restartDate = startDate.plusDays(clockShift).minusDays(1); - final LinkedList expected = new LinkedList(); - - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate)); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30))); - - testBPRepairCreate(false, startDate, clockShift, baseProduct, newBaseProduct, expected); - assertListenerStatus(); - } - - private UUID testBPRepairCreate(final boolean inTrial, final DateTime startDate, final int clockShift, - final String baseProduct, final String newBaseProduct, final List expectedEvents) throws Exception { - // CREATE BP - final SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate); - - // MOVE CLOCK - if (clockShift > 0) { - if (!inTrial) { - testListener.pushExpectedEvent(NextEvent.PHASE); - } - - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(clockShift)); - clock.addDeltaFromReality(it.toDurationMillis()); - if (!inTrial) { - assertListenerStatus(); - } - } - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - final DateTime newCreateTime = baseSubscription.getStartDate().plusDays(clockShift - 1); - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL); - - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CREATE, newCreateTime, spec); - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId())); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - // FIRST ISSUE DRY RUN - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - List subscriptionRepair = dryRunBundleRepair.getSubscriptions(); - assertEquals(subscriptionRepair.size(), 1); - SubscriptionBaseTimeline cur = subscriptionRepair.get(0); - assertEquals(cur.getId(), baseSubscription.getId()); - - List events = cur.getExistingEvents(); - assertEquals(expectedEvents.size(), events.size()); - int index = 0; - for (final ExistingEvent e : expectedEvents) { - testUtil.validateExistingEventForAssertion(e, events.get(index++)); - } - final DefaultSubscriptionBase dryRunBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - - assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId()); - assertTrue(dryRunBaseSubscription.getStartDate().compareTo(baseSubscription.getStartDate()) == 0); - - Plan currentPlan = dryRunBaseSubscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), baseProduct); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase(); - assertNotNull(currentPhase); - if (inTrial) { - assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); - } else { - assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); - } - - // SECOND RE-ISSUE CALL-- NON DRY RUN - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - assertListenerStatus(); - subscriptionRepair = realRunBundleRepair.getSubscriptions(); - assertEquals(subscriptionRepair.size(), 1); - cur = subscriptionRepair.get(0); - assertEquals(cur.getId(), baseSubscription.getId()); - - events = cur.getExistingEvents(); - for (final ExistingEvent e : events) { - log.info(String.format("%s, %s, %s, %s", e.getSubscriptionTransitionType(), e.getEffectiveDate(), e.getPlanPhaseSpecifier().getProductName(), e.getPlanPhaseSpecifier().getPhaseType())); - } - assertEquals(events.size(), expectedEvents.size()); - index = 0; - for (final ExistingEvent e : expectedEvents) { - testUtil.validateExistingEventForAssertion(e, events.get(index++)); - } - final DefaultSubscriptionBase realRunBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(realRunBaseSubscription.getAllTransitions().size(), 2); - - assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId()); - assertEquals(realRunBaseSubscription.getStartDate(), newCreateTime); - - currentPlan = realRunBaseSubscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), newBaseProduct); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - currentPhase = realRunBaseSubscription.getCurrentPhase(); - assertNotNull(currentPhase); - assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); - - return baseSubscription.getId(); - } - - @Test(groups = "slow") - public void testBPRepairAddChangeInTrial() throws Exception { - final String baseProduct = "Shotgun"; - final String newBaseProduct = "Assault-Rifle"; - - final DateTime startDate = clock.getUTCNow(); - final int clockShift = 10; - final DateTime changeDate = startDate.plusDays(clockShift).minusDays(1); - final LinkedList expected = new LinkedList(); - - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, baseProduct, PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, startDate)); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CHANGE, newBaseProduct, PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, changeDate)); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, startDate.plusDays(30))); - - final UUID baseSubscriptionId = testBPRepairAddChange(true, startDate, clockShift, baseProduct, newBaseProduct, expected, 3); - - // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN - testListener.pushExpectedEvent(NextEvent.PHASE); - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscriptionId, internalCallContext); - - assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - assertEquals(subscription.getBundleId(), bundle.getId()); - assertEquals(subscription.getStartDate(), startDate); - assertEquals(subscription.getBundleStartDate(), startDate); - - final Plan currentPlan = subscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), newBaseProduct); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - final PlanPhase currentPhase = subscription.getCurrentPhase(); - assertNotNull(currentPhase); - assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); - - assertListenerStatus(); - } - - @Test(groups = "slow") - public void testBPRepairAddChangeAfterTrial() throws Exception { - final String baseProduct = "Shotgun"; - final String newBaseProduct = "Assault-Rifle"; - - final DateTime startDate = clock.getUTCNow(); - final int clockShift = 40; - final DateTime changeDate = startDate.plusDays(clockShift).minusDays(1); - - final LinkedList expected = new LinkedList(); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, baseProduct, PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, startDate)); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, baseProduct, PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, startDate.plusDays(30))); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CHANGE, newBaseProduct, PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, changeDate)); - testBPRepairAddChange(false, startDate, clockShift, baseProduct, newBaseProduct, expected, 3); - - assertListenerStatus(); - } - - private UUID testBPRepairAddChange(final boolean inTrial, final DateTime startDate, final int clockShift, - final String baseProduct, final String newBaseProduct, final List expectedEvents, final int expectedTransitions) throws Exception { - // CREATE BP - final SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate); - - // MOVE CLOCK - if (!inTrial) { - testListener.pushExpectedEvent(NextEvent.PHASE); - } - - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(clockShift)); - clock.addDeltaFromReality(it.toDurationMillis()); - if (!inTrial) { - assertListenerStatus(); - } - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - final DateTime changeTime = baseSubscription.getStartDate().plusDays(clockShift - 1); - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL); - - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, changeTime, spec); - final List des = new LinkedList(); - if (inTrial) { - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - } - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - // FIRST ISSUE DRY RUN - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - - List subscriptionRepair = dryRunBundleRepair.getSubscriptions(); - assertEquals(subscriptionRepair.size(), 1); - SubscriptionBaseTimeline cur = subscriptionRepair.get(0); - assertEquals(cur.getId(), baseSubscription.getId()); - - List events = cur.getExistingEvents(); - assertEquals(expectedEvents.size(), events.size()); - int index = 0; - for (final ExistingEvent e : expectedEvents) { - testUtil.validateExistingEventForAssertion(e, events.get(index++)); - } - final DefaultSubscriptionBase dryRunBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - - assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId()); - assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate()); - - Plan currentPlan = dryRunBaseSubscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), baseProduct); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase(); - assertNotNull(currentPhase); - if (inTrial) { - assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); - } else { - assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); - } - - // SECOND RE-ISSUE CALL-- NON DRY RUN - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - assertListenerStatus(); - - subscriptionRepair = realRunBundleRepair.getSubscriptions(); - assertEquals(subscriptionRepair.size(), 1); - cur = subscriptionRepair.get(0); - assertEquals(cur.getId(), baseSubscription.getId()); - - events = cur.getExistingEvents(); - assertEquals(expectedEvents.size(), events.size()); - index = 0; - for (final ExistingEvent e : expectedEvents) { - testUtil.validateExistingEventForAssertion(e, events.get(index++)); - } - final DefaultSubscriptionBase realRunBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(realRunBaseSubscription.getAllTransitions().size(), expectedTransitions); - - assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId()); - assertEquals(realRunBaseSubscription.getStartDate(), baseSubscription.getStartDate()); - - currentPlan = realRunBaseSubscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), newBaseProduct); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - currentPhase = realRunBaseSubscription.getCurrentPhase(); - assertNotNull(currentPhase); - if (inTrial) { - assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); - } else { - assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); - } - return baseSubscription.getId(); - } - - @Test(groups = "slow") - public void testRepairWithFutureCancelEvent() throws Exception { - final DateTime startDate = clock.getUTCNow(); - - // CREATE BP - SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate); - - // MOVE CLOCK -- OUT OF TRIAL - testListener.pushExpectedEvent(NextEvent.PHASE); - - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(35)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - - // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT - final DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1); - subscriptionInternalApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, internalCallContext); - baseSubscription = subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - - baseSubscription.changePlan("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, callContext); - - // CHECK CHANGE DID NOT OCCUR YET - Plan currentPlan = baseSubscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), "Shotgun"); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - final DateTime repairTime = clock.getUTCNow().minusDays(1); - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, repairTime, spec); - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(2).getEventId())); - - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - // SKIP DRY RUN AND DO REPAIR... - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - final boolean dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - repairApi.repairBundle(bRepair, dryRun, callContext); - assertListenerStatus(); - - baseSubscription = subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - - assertEquals(((DefaultSubscriptionBase) baseSubscription).getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - assertEquals(baseSubscription.getBundleId(), bundle.getId()); - assertEquals(baseSubscription.getStartDate(), baseSubscription.getStartDate()); - - currentPlan = baseSubscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), "Assault-Rifle"); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - final PlanPhase currentPhase = baseSubscription.getCurrentPhase(); - assertNotNull(currentPhase); - assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); - - assertListenerStatus(); - } - - // Needs real SQL backend to be tested properly - @Test(groups = "slow") - public void testENT_REPAIR_VIEW_CHANGED_newEvent() throws Exception { - final TestWithException test = new TestWithException(); - final DateTime startDate = clock.getUTCNow(); - - final SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate); - - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException { - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec); - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId())); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - testListener.pushExpectedEvent(NextEvent.CHANGE); - final DateTime changeTime = clock.getUTCNow(); - baseSubscription.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, changeTime, callContext); - assertListenerStatus(); - - repairApi.repairBundle(bRepair, true, callContext); - assertListenerStatus(); - } - }, ErrorCode.SUB_REPAIR_VIEW_CHANGED); - } - - @Test(groups = "slow") - public void testENT_REPAIR_VIEW_CHANGED_ctd() throws Exception { - final TestWithException test = new TestWithException(); - final DateTime startDate = clock.getUTCNow(); - - final SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate); - - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException { - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec); - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId())); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - final DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1); - - // Move clock at least a sec to make sure the last_sys_update from bundle is different-- and therefore generates a different viewId - clock.setDeltaFromReality(1000); - - subscriptionInternalApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, internalCallContext); - subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - - repairApi.repairBundle(bRepair, true, callContext); - - assertListenerStatus(); - } - }, ErrorCode.SUB_REPAIR_VIEW_CHANGED); - } -} diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithAO.java b/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithAO.java deleted file mode 100644 index f5d10a99f1..0000000000 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithAO.java +++ /dev/null @@ -1,726 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import org.joda.time.DateTime; -import org.joda.time.Interval; -import org.testng.annotations.Test; - -import org.killbill.billing.api.TestApiListener.NextEvent; -import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.catalog.api.PhaseType; -import org.killbill.billing.catalog.api.Plan; -import org.killbill.billing.catalog.api.PlanPhase; -import org.killbill.billing.catalog.api.PlanPhaseSpecifier; -import org.killbill.billing.catalog.api.PriceListSet; -import org.killbill.billing.catalog.api.ProductCategory; -import org.killbill.billing.entitlement.api.Entitlement.EntitlementState; -import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.DeletedEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.ExistingEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.NewEvent; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; -import org.killbill.billing.subscription.api.user.SubscriptionEvents; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -public class TestRepairWithAO extends SubscriptionTestSuiteWithEmbeddedDB { - - @Test(groups = "slow") - public void testRepairChangeBPWithAddonIncluded() throws Exception { - final String baseProduct = "Shotgun"; - final BillingPeriod baseTerm = BillingPeriod.MONTHLY; - final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME; - - // CREATE BP - final DefaultSubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList); - - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - final DefaultSubscriptionBase aoSubscription2 = testUtil.createSubscription(bundle, "Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3)); - clock.addDeltaFromReality(it.toDurationMillis()); - - BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - // Quick check - SubscriptionBaseTimeline bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - SubscriptionBaseTimeline aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - SubscriptionBaseTimeline aoRepair2 = testUtil.getSubscriptionRepair(aoSubscription2.getId(), bundleRepair); - assertEquals(aoRepair2.getExistingEvents().size(), 2); - - final DateTime bpChangeDate = clock.getUTCNow().minusDays(1); - - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bpRepair.getExistingEvents().get(1).getEventId())); - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, bpChangeDate, spec); - - bpRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - bundleRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, callContext); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - aoRepair2 = testUtil.getSubscriptionRepair(aoSubscription2.getId(), dryRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 3); - - // Check expected for AO - final List expectedAO = new LinkedList(); - expectedAO.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate())); - expectedAO.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate)); - int index = 0; - for (final ExistingEvent e : expectedAO) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - final List expectedAO2 = new LinkedList(); - expectedAO2.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Laser-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription2.getStartDate())); - expectedAO2.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, "Laser-Scope", PhaseType.EVERGREEN, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription2.getStartDate().plusMonths(1))); - index = 0; - for (final ExistingEvent e : expectedAO2) { - testUtil.validateExistingEventForAssertion(e, aoRepair2.getExistingEvents().get(index++)); - } - - // Check expected for BP - final List expectedBP = new LinkedList(); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Shotgun", PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate())); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CHANGE, "Assault-Rifle", PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, bpChangeDate)); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, "Assault-Rifle", PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30))); - index = 0; - for (final ExistingEvent e : expectedBP) { - testUtil.validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++)); - } - - DefaultSubscriptionBase newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - DefaultSubscriptionBase newAoSubscription2 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription2.getId(), internalCallContext); - assertEquals(newAoSubscription2.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription2.getAllTransitions().size(), 2); - assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - DefaultSubscriptionBase newBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(newBaseSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newBaseSubscription.getAllTransitions().size(), 2); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, callContext); - assertListenerStatus(); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 3); - - index = 0; - for (final ExistingEvent e : expectedAO) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - index = 0; - for (final ExistingEvent e : expectedAO2) { - testUtil.validateExistingEventForAssertion(e, aoRepair2.getExistingEvents().get(index++)); - } - - index = 0; - for (final ExistingEvent e : expectedBP) { - testUtil.validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++)); - } - - newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.CANCELLED); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - newAoSubscription2 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription2.getId(), internalCallContext); - assertEquals(newAoSubscription2.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription2.getAllTransitions().size(), 2); - assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - newBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(newBaseSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newBaseSubscription.getAllTransitions().size(), 3); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - } - - @Test(groups = "slow") - public void testRepairChangeBPWithAddonNonAvailable() throws Exception { - final String baseProduct = "Shotgun"; - final BillingPeriod baseTerm = BillingPeriod.MONTHLY; - final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME; - - // CREATE BP - final DefaultSubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList); - - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL - testListener.pushExpectedEvent(NextEvent.PHASE); - testListener.pushExpectedEvent(NextEvent.PHASE); - - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - - BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - // Quick check - SubscriptionBaseTimeline bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - SubscriptionBaseTimeline aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final DateTime bpChangeDate = clock.getUTCNow().minusDays(1); - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, bpChangeDate, spec); - - bpRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), Collections.emptyList(), Collections.singletonList(ne)); - - bundleRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, callContext); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 3); - - bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 3); - - // Check expected for AO - final List expectedAO = new LinkedList(); - expectedAO.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate())); - expectedAO.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Telescopic-Scope", PhaseType.EVERGREEN, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1))); - expectedAO.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate)); - int index = 0; - for (final ExistingEvent e : expectedAO) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - // Check expected for BP - final List expectedBP = new LinkedList(); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Shotgun", PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate())); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30))); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CHANGE, "Pistol", PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate)); - index = 0; - for (final ExistingEvent e : expectedBP) { - testUtil.validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++)); - } - - DefaultSubscriptionBase newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - DefaultSubscriptionBase newBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(newBaseSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newBaseSubscription.getAllTransitions().size(), 2); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, callContext); - assertListenerStatus(); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 3); - - bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 3); - - index = 0; - for (final ExistingEvent e : expectedAO) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - index = 0; - for (final ExistingEvent e : expectedBP) { - testUtil.validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++)); - } - - newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.CANCELLED); - assertEquals(newAoSubscription.getAllTransitions().size(), 3); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - newBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(newBaseSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newBaseSubscription.getAllTransitions().size(), 3); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - } - - @Test(groups = "slow") - public void testRepairCancelBP_EOT_WithAddons() throws Exception { - final String baseProduct = "Shotgun"; - final BillingPeriod baseTerm = BillingPeriod.MONTHLY; - final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME; - - // CREATE BP - DefaultSubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList); - - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL - testListener.pushExpectedEvent(NextEvent.PHASE); - testListener.pushExpectedEvent(NextEvent.PHASE); - - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - - // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT - final DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1); - subscriptionInternalApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, internalCallContext); - baseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - - BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - // Quick check - SubscriptionBaseTimeline bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - SubscriptionBaseTimeline aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final DateTime bpCancelDate = clock.getUTCNow().minusDays(1); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CANCEL, bpCancelDate, null); - bpRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), Collections.emptyList(), Collections.singletonList(ne)); - bundleRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, callContext); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 3); - - bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 3); - - // Check expected for AO - final List expectedAO = new LinkedList(); - expectedAO.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate())); - expectedAO.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1))); - expectedAO.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpCancelDate)); - - int index = 0; - for (final ExistingEvent e : expectedAO) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - // Check expected for BP - final List expectedBP = new LinkedList(); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Shotgun", PhaseType.TRIAL, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate())); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30))); - expectedBP.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CANCEL, "Shotgun", PhaseType.EVERGREEN, - ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpCancelDate)); - index = 0; - for (final ExistingEvent e : expectedBP) { - testUtil.validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++)); - } - - DefaultSubscriptionBase newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - DefaultSubscriptionBase newBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(newBaseSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newBaseSubscription.getAllTransitions().size(), 2); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, callContext); - assertListenerStatus(); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 3); - - bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 3); - - index = 0; - for (final ExistingEvent e : expectedAO) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - index = 0; - for (final ExistingEvent e : expectedBP) { - testUtil.validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++)); - } - - newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.CANCELLED); - assertEquals(newAoSubscription.getAllTransitions().size(), 3); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - newBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(newBaseSubscription.getState(), EntitlementState.CANCELLED); - assertEquals(newBaseSubscription.getAllTransitions().size(), 3); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - } - - @Test(groups = "slow") - public void testRepairCancelAO() throws Exception { - final String baseProduct = "Shotgun"; - final BillingPeriod baseTerm = BillingPeriod.MONTHLY; - final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME; - - // CREATE BP - final DefaultSubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList); - - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - // Quick check - SubscriptionBaseTimeline bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - SubscriptionBaseTimeline aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId())); - final DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(1); - - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CANCEL, aoCancelDate, null); - - final SubscriptionBaseTimeline saoRepair = testUtil.createSubscriptionRepair(aoSubscription.getId(), des, Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - final List expected = new LinkedList(); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate())); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoCancelDate)); - int index = 0; - for (final ExistingEvent e : expected) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - DefaultSubscriptionBase newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - DefaultSubscriptionBase newBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(newBaseSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newBaseSubscription.getAllTransitions().size(), 2); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - assertListenerStatus(); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - index = 0; - for (final ExistingEvent e : expected) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.CANCELLED); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - newBaseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext); - assertEquals(newBaseSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newBaseSubscription.getAllTransitions().size(), 2); - assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - } - - @Test(groups = "slow") - public void testRepairRecreateAO() throws Exception { - final String baseProduct = "Shotgun"; - final BillingPeriod baseTerm = BillingPeriod.MONTHLY; - final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME; - - // CREATE BP - final DefaultSubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList); - - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - // Quick check - final SubscriptionBaseTimeline bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - SubscriptionBaseTimeline aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId())); - des.add(testUtil.createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId())); - - final DateTime aoRecreateDate = aoSubscription.getStartDate().plusDays(1); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CREATE, aoRecreateDate, spec); - - final SubscriptionBaseTimeline saoRepair = testUtil.createSubscriptionRepair(aoSubscription.getId(), des, Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final List expected = new LinkedList(); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoRecreateDate)); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1) /* Bundle align */)); - int index = 0; - for (final ExistingEvent e : expected) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - DefaultSubscriptionBase newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getStartDate(), aoSubscription.getStartDate()); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); - - // NOW COMMIT - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - assertListenerStatus(); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - index = 0; - for (final ExistingEvent e : expected) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - assertEquals(newAoSubscription.getStartDate(), aoRecreateDate); - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - - } - - // Fasten your seatbelt here: - // - // We are doing repair for multi-phase tiered-addon with different alignment: - // Telescopic-Scope -> Laser-Scope - // Tiered ADON logic - // . Both multi phase - // . Telescopic-Scope (bundle align) and Laser-Scope is SubscriptionBase align - // - @Test(groups = "slow") - public void testRepairChangeAOOK() throws Exception { - final String baseProduct = "Shotgun"; - final BillingPeriod baseTerm = BillingPeriod.MONTHLY; - final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME; - - // CREATE BP - final DefaultSubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList); - - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - // Quick check - final SubscriptionBaseTimeline bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - SubscriptionBaseTimeline aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId())); - final DateTime aoChangeDate = aoSubscription.getStartDate().plusDays(1); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, aoChangeDate, spec); - - final SubscriptionBaseTimeline saoRepair = testUtil.createSubscriptionRepair(aoSubscription.getId(), des, Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair)); - - boolean dryRun = true; - final BundleBaseTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 3); - - final List expected = new LinkedList(); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate())); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.CHANGE, "Laser-Scope", PhaseType.DISCOUNT, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoChangeDate)); - expected.add(testUtil.createExistingEventForAssertion(SubscriptionBaseTransitionType.PHASE, "Laser-Scope", PhaseType.EVERGREEN, - ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, - aoSubscription.getStartDate().plusMonths(1) /* SubscriptionBase alignment */)); - - int index = 0; - for (final ExistingEvent e : expected) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - DefaultSubscriptionBase newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription.getAllTransitions().size(), 2); - - // AND NOW COMMIT - dryRun = false; - testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE); - final BundleBaseTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, callContext); - assertListenerStatus(); - - aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 3); - index = 0; - for (final ExistingEvent e : expected) { - testUtil.validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++)); - } - - newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - assertEquals(newAoSubscription.getState(), EntitlementState.ACTIVE); - assertEquals(newAoSubscription.getAllTransitions().size(), 3); - - assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1); - assertEquals(newAoSubscription.getBundleId(), bundle.getId()); - assertEquals(newAoSubscription.getStartDate(), aoSubscription.getStartDate()); - - final Plan currentPlan = newAoSubscription.getCurrentPlan(); - assertNotNull(currentPlan); - assertEquals(currentPlan.getProduct().getName(), "Laser-Scope"); - assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.ADD_ON); - assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY); - - PlanPhase currentPhase = newAoSubscription.getCurrentPhase(); - assertNotNull(currentPhase); - assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT); - - // One phase for BP an one phase for the new AO (laser-scope) - testListener.pushExpectedEvent(NextEvent.PHASE); - testListener.pushExpectedEvent(NextEvent.PHASE); - - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(60)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - - newAoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext); - currentPhase = newAoSubscription.getCurrentPhase(); - assertNotNull(currentPhase); - assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); - } -} diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithError.java deleted file mode 100644 index 5b81058aa0..0000000000 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithError.java +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.subscription.api.timeline; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import org.joda.time.DateTime; -import org.joda.time.Interval; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import org.killbill.billing.ErrorCode; -import org.killbill.billing.api.TestApiListener.NextEvent; -import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.catalog.api.PhaseType; -import org.killbill.billing.catalog.api.PlanPhaseSpecifier; -import org.killbill.billing.catalog.api.PriceListSet; -import org.killbill.billing.catalog.api.ProductCategory; -import org.killbill.billing.subscription.SubscriptionTestSuiteNoDB; -import org.killbill.billing.subscription.api.SubscriptionBase; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.DeletedEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.NewEvent; -import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; -import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException; -import org.killbill.billing.subscription.api.user.TestSubscriptionHelper.TestWithException; -import org.killbill.billing.subscription.api.user.TestSubscriptionHelper.TestWithExceptionCallback; -import org.killbill.billing.util.UUIDs; - -import static org.testng.Assert.assertEquals; - -public class TestRepairWithError extends SubscriptionTestSuiteNoDB { - - private static final String baseProduct = "Shotgun"; - private TestWithException test; - private SubscriptionBase baseSubscription; - - @Override - @BeforeMethod(groups = "fast") - public void beforeMethod() throws Exception { - super.beforeMethod(); - test = new TestWithException(); - final DateTime startDate = clock.getUTCNow(); - baseSubscription = testUtil.createSubscription(bundle, baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate); - } - - @Test(groups = "fast") - public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException { - // MOVE AFTER TRIAL - testListener.pushExpectedEvent(NextEvent.PHASE); - - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40)); - clock.addDeltaFromReality(it.toDurationMillis()); - - assertListenerStatus(); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec); - - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), Collections.emptyList(), Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - repairApi.repairBundle(bRepair, true, callContext); - } - }, ErrorCode.SUB_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING); - } - - @Test(groups = "fast") - public void testENT_REPAIR_INVALID_DELETE_SET() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException { - - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3)); - clock.addDeltaFromReality(it.toDurationMillis()); - - testListener.pushExpectedEvent(NextEvent.CHANGE); - final DateTime changeTime = clock.getUTCNow(); - baseSubscription.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, changeTime, callContext); - assertListenerStatus(); - - // MOVE AFTER TRIAL - testListener.pushExpectedEvent(NextEvent.PHASE); - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec); - final DeletedEvent de = testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()); - - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne)); - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - repairApi.repairBundle(bRepair, true, callContext); - } - }, ErrorCode.SUB_REPAIR_INVALID_DELETE_SET); - } - - @Test(groups = "fast") - public void testENT_REPAIR_NON_EXISTENT_DELETE_EVENT() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException { - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec); - final DeletedEvent de = testUtil.createDeletedEvent(UUIDs.randomUUID()); - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - repairApi.repairBundle(bRepair, true, callContext); - } - }, ErrorCode.SUB_REPAIR_NON_EXISTENT_DELETE_EVENT); - } - - @Test(groups = "fast") - public void testENT_REPAIR_SUB_RECREATE_NOT_EMPTY() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException { - - // MOVE AFTER TRIAL - testListener.pushExpectedEvent(NextEvent.PHASE); - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CREATE, baseSubscription.getStartDate().plusDays(10), spec); - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - repairApi.repairBundle(bRepair, true, callContext); - - } - }, ErrorCode.SUB_REPAIR_SUB_RECREATE_NOT_EMPTY); - } - - @Test(groups = "fast") - public void testENT_REPAIR_SUB_EMPTY() throws Exception { - test.withException(new TestWithExceptionCallback() { - - @Override - public void doTest() throws SubscriptionBaseRepairException { - - // MOVE AFTER TRIAL - testListener.pushExpectedEvent(NextEvent.PHASE); - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40)); - clock.addDeltaFromReality(it.toDurationMillis()); - assertListenerStatus(); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec); - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId())); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - repairApi.repairBundle(bRepair, true, callContext); - } - }, ErrorCode.SUB_REPAIR_SUB_EMPTY); - } - - @Test(groups = "fast") - public void testENT_REPAIR_AO_CREATE_BEFORE_BP_START() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException { - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - // Quick check - final SubscriptionBaseTimeline bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - final SubscriptionBaseTimeline aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId())); - des.add(testUtil.createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId())); - - final DateTime aoRecreateDate = aoSubscription.getStartDate().minusDays(5); - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT); - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CREATE, aoRecreateDate, spec); - - final SubscriptionBaseTimeline saoRepair = testUtil.createSubscriptionRepair(aoSubscription.getId(), des, Collections.singletonList(ne)); - - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair)); - - final boolean dryRun = true; - repairApi.repairBundle(bRepair, dryRun, callContext); - } - }, ErrorCode.SUB_REPAIR_AO_CREATE_BEFORE_BP_START); - } - - @Test(groups = "fast") - public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException { - - // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL - it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - - BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - // Quick check - final SubscriptionBaseTimeline bpRepair = testUtil.getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - assertEquals(bpRepair.getExistingEvents().size(), 2); - - final SubscriptionBaseTimeline aoRepair = testUtil.getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - assertEquals(aoRepair.getExistingEvents().size(), 2); - - final List des = new LinkedList(); - //des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId())); - final DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(10); - - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CANCEL, aoCancelDate, null); - - final SubscriptionBaseTimeline saoRepair = testUtil.createSubscriptionRepair(aoSubscription.getId(), des, Collections.singletonList(ne)); - - bundleRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair)); - - final boolean dryRun = true; - repairApi.repairBundle(bundleRepair, dryRun, callContext); - } - }, ErrorCode.SUB_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING); - } - - @Test(groups = "fast", enabled = false) // TODO - fails on jdk7 on Travis - public void testENT_REPAIR_BP_RECREATE_MISSING_AO() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException { - - //testListener.pushExpectedEvent(NextEvent.PHASE); - - final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - //assertListenerStatus(); - - final DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, "Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - final BundleBaseTimeline bundleRepair = repairApi.getBundleTimeline(bundle.getId(), callContext); - testUtil.sortEventsOnBundle(bundleRepair); - - final DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3); - - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL); - - final NewEvent ne = testUtil.createNewEvent(SubscriptionBaseTransitionType.CREATE, newCreateTime, spec); - final List des = new LinkedList(); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId())); - des.add(testUtil.createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - - final SubscriptionBaseTimeline sRepair = testUtil.createSubscriptionRepair(baseSubscription.getId(), des, Collections.singletonList(ne)); - - // FIRST ISSUE DRY RUN - final BundleBaseTimeline bRepair = testUtil.createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair)); - - final boolean dryRun = true; - repairApi.repairBundle(bRepair, dryRun, callContext); - } - }, ErrorCode.SUB_REPAIR_BP_RECREATE_MISSING_AO); - } - - // - // CAN'T seem to trigger such case easily, other errors trigger before... - // - @Test(groups = "fast", enabled = false) - public void testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException { - /* - //testListener.pushExpectedEvent(NextEvent.PHASE); - - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4)); - clock.addDeltaFromReality(it.toDurationMillis()); - - - DefaultSubscriptionBase aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId()); - sortEventsOnBundle(bundleRepair); - - DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3); - - PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL); - - NewEvent ne = createNewEvent(SubscriptionBaseTransitionType.CREATE, newCreateTime, spec); - List des = new LinkedList(); - des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId())); - des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId())); - - SubscriptionRepair bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne)); - - ne = createNewEvent(SubscriptionBaseTransitionType.CANCEL, clock.getUTCNow().minusDays(1), null); - SubscriptionRepair aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.emptyList(), Collections.singletonList(ne)); - - - List allRepairs = new LinkedList(); - allRepairs.add(bpRepair); - allRepairs.add(aoRepair); - bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs); - // FIRST ISSUE DRY RUN - BundleRepair bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs); - - boolean dryRun = true; - repairApi.repairBundle(bRepair, dryRun, callcontext); - */ - } - }, ErrorCode.SUB_REPAIR_BP_RECREATE_MISSING_AO_CREATE); - } - - @Test(groups = "fast", enabled = false) - public void testENT_REPAIR_MISSING_AO_DELETE_EVENT() throws Exception { - test.withException(new TestWithExceptionCallback() { - @Override - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException { - - /* - // MOVE CLOCK -- JUST BEFORE END OF TRIAL - * - Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(29)); - clock.addDeltaFromReality(it.toDurationMillis()); - - clock.setDeltaFromReality(getDurationDay(29), 0); - - DefaultSubscriptionBase aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME); - - // MOVE CLOCK -- RIGHT OUT OF TRIAL - testListener.pushExpectedEvent(NextEvent.PHASE); - clock.addDeltaFromReality(getDurationDay(5)); - assertListenerStatus(); - - DateTime requestedChange = clock.getUTCNow(); - baseSubscription.changePlanWithRequestedDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, requestedChange, callcontext); - - DateTime reapairTime = clock.getUTCNow().minusDays(1); - - BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId()); - sortEventsOnBundle(bundleRepair); - - SubscriptionRepair bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair); - SubscriptionRepair aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair); - - List bpdes = new LinkedList(); - bpdes.add(createDeletedEvent(bpRepair.getExistingEvents().get(2).getEventId())); - bpRepair = createSubscriptionReapir(baseSubscription.getId(), bpdes, Collections.emptyList()); - - NewEvent ne = createNewEvent(SubscriptionBaseTransitionType.CANCEL, reapairTime, null); - aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.emptyList(), Collections.singletonList(ne)); - - List allRepairs = new LinkedList(); - allRepairs.add(bpRepair); - allRepairs.add(aoRepair); - bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs); - - boolean dryRun = false; - repairApi.repairBundle(bundleRepair, dryRun, callcontext); - */ - } - }, ErrorCode.SUB_REPAIR_MISSING_AO_DELETE_EVENT); - } - -} diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java index 0cea1b8750..3034a362f6 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java @@ -19,8 +19,6 @@ package org.killbill.billing.subscription.api.user; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -30,7 +28,6 @@ import org.joda.time.DateTime; import org.joda.time.Period; -import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountUserApi; @@ -47,17 +44,10 @@ import org.killbill.billing.events.EffectiveSubscriptionInternalEvent; import org.killbill.billing.mock.MockAccountBuilder; import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; import org.killbill.billing.subscription.api.migration.SubscriptionBaseMigrationApi.AccountMigration; import org.killbill.billing.subscription.api.migration.SubscriptionBaseMigrationApi.BundleMigration; import org.killbill.billing.subscription.api.migration.SubscriptionBaseMigrationApi.SubscriptionMigration; import org.killbill.billing.subscription.api.migration.SubscriptionBaseMigrationApi.SubscriptionMigrationCase; -import org.killbill.billing.subscription.api.timeline.BundleBaseTimeline; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseRepairException; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.DeletedEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.ExistingEvent; -import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.NewEvent; import org.killbill.billing.subscription.engine.dao.SubscriptionDao; import org.killbill.billing.subscription.events.SubscriptionBaseEvent; import org.killbill.billing.subscription.events.phase.PhaseEvent; @@ -67,7 +57,6 @@ import org.killbill.clock.Clock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testng.Assert; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -151,31 +140,6 @@ public void assertDateWithin(final DateTime in, final DateTime lower, final Date assertTrue(in.isEqual(upper) || in.isBefore(upper)); } - public Duration getDurationDay(final int days) { - final Duration result = new Duration() { - @Override - public TimeUnit getUnit() { - return TimeUnit.DAYS; - } - - @Override - public int getNumber() { - return days; - } - - @Override - public DateTime addToDateTime(final DateTime dateTime) { - return null; - } - - @Override - public Period toJodaPeriod() { - throw new UnsupportedOperationException(); - } - }; - return result; - } - public Duration getDurationMonth(final int months) { final Duration result = new Duration() { @Override @@ -201,31 +165,6 @@ public Period toJodaPeriod() { return result; } - public Duration getDurationYear(final int years) { - final Duration result = new Duration() { - @Override - public TimeUnit getUnit() { - return TimeUnit.YEARS; - } - - @Override - public int getNumber() { - return years; - } - - @Override - public DateTime addToDateTime(final DateTime dateTime) { - return dateTime.plusYears(years); - } - - @Override - public Period toJodaPeriod() { - throw new UnsupportedOperationException(); - } - }; - return result; - } - public PlanPhaseSpecifier getProductSpecifier(final String productName, final String priceList, final BillingPeriod term, @Nullable final PhaseType phaseType) { @@ -401,206 +340,6 @@ public AccountMigration createAccountForMigrationFuturePendingChange() { return createAccountForMigrationTest(input); } - public SubscriptionBaseTimeline createSubscriptionRepair(final UUID id, final List deletedEvents, final List newEvents) { - return new SubscriptionBaseTimeline() { - @Override - public UUID getId() { - return id; - } - - @Override - public DateTime getCreatedDate() { - return null; - } - - @Override - public DateTime getUpdatedDate() { - return null; - } - - @Override - public List getNewEvents() { - return newEvents; - } - - @Override - public List getExistingEvents() { - return null; - } - - @Override - public List getDeletedEvents() { - return deletedEvents; - } - - @Override - public long getActiveVersion() { - return 1; - } - }; - } - - public BundleBaseTimeline createBundleRepair(final UUID bundleId, final String viewId, final List subscriptionRepair) { - return new BundleBaseTimeline() { - @Override - public String getViewId() { - return viewId; - } - - @Override - public List getSubscriptions() { - return subscriptionRepair; - } - - @Override - public UUID getId() { - return bundleId; - } - - @Override - public DateTime getCreatedDate() { - return null; - } - - @Override - public DateTime getUpdatedDate() { - return null; - } - - @Override - public String getExternalKey() { - return null; - } - }; - } - - public ExistingEvent createExistingEventForAssertion(final SubscriptionBaseTransitionType type, - final String productName, final PhaseType phaseType, final ProductCategory category, final String priceListName, final BillingPeriod billingPeriod, - final DateTime effectiveDateTime) { - final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType); - return new ExistingEvent() { - @Override - public SubscriptionBaseTransitionType getSubscriptionTransitionType() { - return type; - } - - @Override - public DateTime getRequestedDate() { - return null; - } - - @Override - public PlanPhaseSpecifier getPlanPhaseSpecifier() { - return spec; - } - - @Override - public UUID getEventId() { - return null; - } - - @Override - public DateTime getEffectiveDate() { - return effectiveDateTime; - } - - @Override - public String getPlanName() { - return null; - } - - @Override - public String getPlanPhaseName() { - return null; - } - }; - } - - public SubscriptionBaseTimeline getSubscriptionRepair(final UUID id, final BundleBaseTimeline bundleRepair) { - for (final SubscriptionBaseTimeline cur : bundleRepair.getSubscriptions()) { - if (cur.getId().equals(id)) { - return cur; - } - } - Assert.fail("Failed to find SubscriptionRepair " + id); - return null; - } - - public void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) { - log.debug(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName())); - assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()); - log.debug(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType())); - assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()); - log.debug(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory())); - assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()); - log.debug(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName())); - assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()); - log.debug(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod())); - assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()); - log.debug(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate())); - assertEquals(input.getEffectiveDate(), expected.getEffectiveDate()); - } - - public DeletedEvent createDeletedEvent(final UUID eventId) { - return new DeletedEvent() { - @Override - public UUID getEventId() { - return eventId; - } - }; - } - - public NewEvent createNewEvent(final SubscriptionBaseTransitionType type, final DateTime requestedDate, final PlanPhaseSpecifier spec) { - return new NewEvent() { - @Override - public SubscriptionBaseTransitionType getSubscriptionTransitionType() { - return type; - } - - @Override - public DateTime getRequestedDate() { - return requestedDate; - } - - @Override - public PlanPhaseSpecifier getPlanPhaseSpecifier() { - return spec; - } - }; - } - - public void sortEventsOnBundle(final BundleBaseTimeline bundle) { - if (bundle.getSubscriptions() == null) { - return; - } - for (final SubscriptionBaseTimeline cur : bundle.getSubscriptions()) { - if (cur.getExistingEvents() != null) { - sortExistingEvent(cur.getExistingEvents()); - } - if (cur.getNewEvents() != null) { - sortNewEvent(cur.getNewEvents()); - } - } - } - - public void sortExistingEvent(final List events) { - Collections.sort(events, new Comparator() { - @Override - public int compare(final ExistingEvent arg0, final ExistingEvent arg1) { - return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate()); - } - }); - } - - public void sortNewEvent(final List events) { - Collections.sort(events, new Comparator() { - @Override - public int compare(final NewEvent arg0, final NewEvent arg1) { - return arg0.getRequestedDate().compareTo(arg1.getRequestedDate()); - } - }); - } - public static DateTime addOrRemoveDuration(final DateTime input, final List durations, final boolean add) { DateTime result = input; for (final Duration cur : durations) { @@ -628,22 +367,12 @@ public static DateTime addDuration(final DateTime input, final List du return addOrRemoveDuration(input, durations, true); } - public static DateTime removeDuration(final DateTime input, final List durations) { - return addOrRemoveDuration(input, durations, false); - } - public static DateTime addDuration(final DateTime input, final Duration duration) { final List list = new ArrayList(); list.add(duration); return addOrRemoveDuration(input, list, true); } - public static DateTime removeDuration(final DateTime input, final Duration duration) { - final List list = new ArrayList(); - list.add(duration); - return addOrRemoveDuration(input, list, false); - } - public static class SubscriptionMigrationCaseWithCTD implements SubscriptionMigrationCase { private final PlanPhaseSpecifier pps; @@ -678,20 +407,4 @@ public DateTime getChargedThroughDate() { } } - public interface TestWithExceptionCallback { - - public void doTest() throws SubscriptionBaseRepairException, SubscriptionBaseApiException; - } - - public static class TestWithException { - - public void withException(final TestWithExceptionCallback callback, final ErrorCode code) throws Exception { - try { - callback.doTest(); - Assert.fail("Failed to catch exception " + code); - } catch (SubscriptionBaseRepairException e) { - assertEquals(e.getCode(), code.getCode()); - } - } - } } diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java index 1891725161..9b608c0c46 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java @@ -41,7 +41,6 @@ import org.killbill.billing.subscription.api.migration.AccountMigrationData; import org.killbill.billing.subscription.api.migration.AccountMigrationData.BundleMigrationData; import org.killbill.billing.subscription.api.migration.AccountMigrationData.SubscriptionMigrationData; -import org.killbill.billing.subscription.api.timeline.SubscriptionDataRepair; import org.killbill.billing.subscription.api.transfer.TransferCancelData; import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle; @@ -485,11 +484,6 @@ public Iterable getFutureEventsForAccount(final InternalT return null; } - @Override - public void repair(final UUID accountId, final UUID bundleId, final List inRepair, - final InternalCallContext context) { - } - @Override public void transfer(final UUID srcAccountId, final UUID destAccountId, final BundleMigrationData data, final List transferCancelData, final InternalCallContext fromContext, diff --git a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleNoDB.java b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleNoDB.java index ae1d4b7207..1bb0955e34 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleNoDB.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleNoDB.java @@ -22,14 +22,10 @@ import org.killbill.billing.account.api.AccountUserApi; import org.killbill.billing.mock.glue.MockNonEntityDaoModule; import org.killbill.billing.platform.api.KillbillConfigSource; -import org.killbill.billing.subscription.api.timeline.RepairSubscriptionLifecycleDao; import org.killbill.billing.subscription.engine.dao.MockSubscriptionDaoMemory; -import org.killbill.billing.subscription.engine.dao.RepairSubscriptionDao; import org.killbill.billing.subscription.engine.dao.SubscriptionDao; import org.mockito.Mockito; -import com.google.inject.name.Names; - public class TestDefaultSubscriptionModuleNoDB extends TestDefaultSubscriptionModule { public TestDefaultSubscriptionModuleNoDB(final KillbillConfigSource configSource) { @@ -39,9 +35,6 @@ public TestDefaultSubscriptionModuleNoDB(final KillbillConfigSource configSource @Override protected void installSubscriptionDao() { bind(SubscriptionDao.class).to(MockSubscriptionDaoMemory.class).asEagerSingleton(); - bind(SubscriptionDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionDao.class); - bind(RepairSubscriptionLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionDao.class); - bind(RepairSubscriptionDao.class).asEagerSingleton(); } @Override diff --git a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleWithEmbeddedDB.java b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleWithEmbeddedDB.java index 6d8cfa2eb3..7918876bf6 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleWithEmbeddedDB.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleWithEmbeddedDB.java @@ -21,15 +21,11 @@ import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule; import org.killbill.billing.account.glue.DefaultAccountModule; import org.killbill.billing.platform.api.KillbillConfigSource; -import org.killbill.billing.subscription.api.timeline.RepairSubscriptionLifecycleDao; import org.killbill.billing.subscription.engine.dao.MockSubscriptionDaoSql; -import org.killbill.billing.subscription.engine.dao.RepairSubscriptionDao; import org.killbill.billing.subscription.engine.dao.SubscriptionDao; import org.killbill.billing.util.glue.CustomFieldModule; import org.killbill.billing.util.glue.NonEntityDaoModule; -import com.google.inject.name.Names; - public class TestDefaultSubscriptionModuleWithEmbeddedDB extends TestDefaultSubscriptionModule { public TestDefaultSubscriptionModuleWithEmbeddedDB(final KillbillConfigSource configSource) { @@ -39,9 +35,6 @@ public TestDefaultSubscriptionModuleWithEmbeddedDB(final KillbillConfigSource co @Override protected void installSubscriptionDao() { bind(SubscriptionDao.class).to(MockSubscriptionDaoSql.class).asEagerSingleton(); - bind(SubscriptionDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionDao.class); - bind(RepairSubscriptionLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionDao.class); - bind(RepairSubscriptionDao.class).asEagerSingleton(); } @Override From 7a0a5c97a2c6dbae50a5a99b5a2c2228f70b13f0 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 19 Aug 2015 15:53:20 -0700 Subject: [PATCH 024/137] Add new filter to attach a ThreadLocal RequestId (and provide support for plugin thread switch) --- .../billing/payment/core/ProcessorBase.java | 2 + .../dispatcher/CallableWithRequestData.java | 44 ++++++++++++++++ .../payment/dispatcher/PluginDispatcher.java | 6 ++- .../billing/payment/glue/PaymentModule.java | 6 ++- .../dispatcher/TestPluginDispatcher.java | 25 +++++++++ pom.xml | 2 +- .../server/filters/RequestIdFilter.java | 51 +++++++++++++++++++ .../listeners/KillbillGuiceListener.java | 2 + 8 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java create mode 100644 profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java index f8c2d675d1..5a23cabe30 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java @@ -41,6 +41,7 @@ import org.killbill.billing.payment.dao.PaymentDao; import org.killbill.billing.payment.dao.PaymentMethodModelDao; import org.killbill.billing.payment.dao.PaymentTransactionModelDao; +import org.killbill.billing.payment.dispatcher.CallableWithRequestData; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; import org.killbill.billing.payment.plugin.api.PaymentPluginApi; @@ -56,6 +57,7 @@ import org.killbill.commons.locker.GlobalLock; import org.killbill.commons.locker.GlobalLocker; import org.killbill.commons.locker.LockFailedException; +import org.killbill.commons.request.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java new file mode 100644 index 0000000000..e78d8ade26 --- /dev/null +++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.payment.dispatcher; + +import java.util.concurrent.Callable; + +import org.killbill.commons.request.Request; +import org.killbill.commons.request.RequestData; + +public class CallableWithRequestData implements Callable { + + private final RequestData requestData; + private final Callable delegate; + + public CallableWithRequestData(final RequestData requestData, final Callable delegate) { + this.requestData = requestData; + this.delegate = delegate; + } + + @Override + public T call() throws Exception { + try { + Request.setPerThreadRequestData(requestData); + return delegate.call(); + } finally { + Request.resetPerThreadRequestData(); + } + } +} diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java index a60eff6f78..f8b6ca511a 100644 --- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java +++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java @@ -27,6 +27,7 @@ import org.killbill.commons.profiling.Profiling; import org.killbill.commons.profiling.ProfilingData; +import org.killbill.commons.request.Request; public class PluginDispatcher { @@ -48,7 +49,10 @@ public ReturnType dispatchWithTimeout(final Callable> task, final long timeout, final TimeUnit unit) throws TimeoutException, ExecutionException, InterruptedException { - final Future> future = executor.submit(task); + // Wrap existing callable to keep the original requestId + final Callable> callableWithRequestData = new CallableWithRequestData(Request.getPerThreadRequestData(), task); + + final Future> future = executor.submit(callableWithRequestData); final PluginDispatcherReturnType pluginDispatcherResult = future.get(timeout, unit); if (pluginDispatcherResult instanceof WithProfilingPluginDispatcherReturnType) { diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java index 0c0b0a76c5..bd95ba473f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java +++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java @@ -136,8 +136,10 @@ protected void installAutomatonRunner() { protected void installProcessors(final PaymentConfig paymentConfig) { - final ExecutorService pluginExecutorService = new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(), paymentConfig.getPaymentPluginThreadNb(), - 0L, TimeUnit.MILLISECONDS, + final ExecutorService pluginExecutorService = new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(), + paymentConfig.getPaymentPluginThreadNb(), + 0L, + TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactory() { diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java index d226d736b9..aef5eec41a 100644 --- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java +++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java @@ -26,6 +26,8 @@ import org.killbill.billing.payment.PaymentTestSuiteNoDB; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; +import org.killbill.commons.request.Request; +import org.killbill.commons.request.RequestData; import org.testng.Assert; import org.testng.annotations.Test; @@ -33,6 +35,8 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB { private final PluginDispatcher voidPluginDispatcher = new PluginDispatcher(10, Executors.newSingleThreadExecutor()); + private final PluginDispatcher stringPluginDispatcher = new PluginDispatcher(1, Executors.newSingleThreadExecutor()); + @Test(groups = "fast") public void testDispatchWithTimeout() throws TimeoutException, PaymentApiException { boolean gotIt = false; @@ -106,4 +110,25 @@ public PluginDispatcherReturnType call() throws Exception { } Assert.assertTrue(gotIt); } + + + @Test(groups = "fast") + public void testDispatchWithRequestData() throws TimeoutException, PaymentApiException, ExecutionException, InterruptedException { + + final String requestId = "vive la vie et les coquillettes"; + + final Callable> delegate = new Callable>() { + @Override + public PluginDispatcherReturnType call() throws Exception { + return PluginDispatcher.createPluginDispatcherReturnType(Request.getPerThreadRequestData().getRequestId()); + } + }; + + final CallableWithRequestData> callable = new CallableWithRequestData>(new RequestData(requestId), + delegate); + + final String actualRequestId = stringPluginDispatcher.dispatchWithTimeout(callable, 100, TimeUnit.MILLISECONDS); + Assert.assertEquals(actualRequestId, requestId); + } + } diff --git a/pom.xml b/pom.xml index 6b6b458520..8deb6da3a6 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.37 + 0.38-SNAPSHOT killbill 0.15.3-SNAPSHOT diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java new file mode 100644 index 0000000000..3cfe141ca0 --- /dev/null +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.server.filters; + +import java.util.List; + +import org.killbill.billing.util.UUIDs; +import org.killbill.commons.request.Request; +import org.killbill.commons.request.RequestData; + +import com.google.inject.Singleton; +import com.sun.jersey.spi.container.ContainerRequest; +import com.sun.jersey.spi.container.ContainerRequestFilter; +import com.sun.jersey.spi.container.ContainerResponse; +import com.sun.jersey.spi.container.ContainerResponseFilter; + +@Singleton +public class RequestIdFilter implements ContainerRequestFilter, ContainerResponseFilter { + + + private static final String REQUEST_ID_HEADER_REQ = "X-Killbill-Request-Id-Req"; + + @Override + public ContainerRequest filter(final ContainerRequest request) { + final List requestIdHeaderRequests = request.getRequestHeader(REQUEST_ID_HEADER_REQ); + final String requestId = (requestIdHeaderRequests == null || requestIdHeaderRequests.isEmpty()) ? UUIDs.randomUUID().toString() : requestIdHeaderRequests.get(0); + Request.setPerThreadRequestData(new RequestData(requestId)); + return request; + } + + @Override + public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) { + Request.resetPerThreadRequestData(); + return response; + } +} diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java index 6e506da45e..bca802ad93 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java @@ -28,6 +28,7 @@ import org.killbill.billing.platform.api.KillbillConfigSource; import org.killbill.billing.platform.config.DefaultKillbillConfigSource; import org.killbill.billing.server.filters.ProfilingContainerResponseFilter; +import org.killbill.billing.server.filters.RequestIdFilter; import org.killbill.billing.server.filters.ResponseCorsFilter; import org.killbill.billing.server.modules.KillbillServerModule; import org.killbill.billing.server.security.TenantFilter; @@ -73,6 +74,7 @@ protected ServletModule getServletModule() { // The logging filter is still incompatible with the GZIP filter //builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName()); builder.addJerseyFilter(ProfilingContainerResponseFilter.class.getName()); + builder.addJerseyFilter(RequestIdFilter.class.getName()); // Broader, to support the "Try it out!" feature //builder.addFilter("/" + SWAGGER_PATH + "*", ResponseCorsFilter.class); From 1df0daf25d0476011a81a3d901116b9fde3072d6 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 19 Aug 2015 17:28:35 -0700 Subject: [PATCH 025/137] jaxrs: add support for control plugins in PaymentGatewayResource Signed-off-by: Pierre-Alexandre Meyer --- .../jaxrs/resources/PaymentGatewayResource.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java index 7f50aa1ad4..62b723e852 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java @@ -44,6 +44,7 @@ import org.killbill.billing.payment.api.PaymentApi; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.api.PaymentGatewayApi; +import org.killbill.billing.payment.api.PaymentOptions; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.plugin.api.GatewayNotification; import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor; @@ -94,6 +95,7 @@ public PaymentGatewayResource(final JaxrsUriBuilder uriBuilder, @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid data for Account or PaymentMethod")}) public Response buildComboFormDescriptor(final ComboHostedPaymentPageJson json, @PathParam("accountId") final String accountIdString, + @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List paymentControlPluginNames, @QueryParam(QUERY_PLUGIN_PROPERTY) final List pluginPropertiesString, @HeaderParam(HDR_CREATED_BY) final String createdBy, @HeaderParam(HDR_REASON) final String reason, @@ -103,6 +105,7 @@ public Response buildComboFormDescriptor(final ComboHostedPaymentPageJson json, verifyNonNullOrEmpty(json, "ComboHostedPaymentPageJson body should be specified"); final Iterable pluginProperties = extractPluginProperties(pluginPropertiesString); + final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames); final CallContext callContext = context.createContext(createdBy, reason, comment, request); final Account account = getOrCreateAccount(json.getAccount(), callContext); @@ -113,7 +116,7 @@ public Response buildComboFormDescriptor(final ComboHostedPaymentPageJson json, final HostedPaymentPageFieldsJson hostedPaymentPageFields = json.getHostedPaymentPageFieldsJson(); final Iterable customFields = extractPluginProperties(hostedPaymentPageFields != null ? hostedPaymentPageFields.getCustomFields() : null); - final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, paymentMethodId, customFields, pluginProperties, callContext); + final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptorWithPaymentControl(account, paymentMethodId, customFields, pluginProperties, paymentOptions, callContext); final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor); return Response.status(Response.Status.OK).entity(result).build(); @@ -130,6 +133,7 @@ public Response buildComboFormDescriptor(final ComboHostedPaymentPageJson json, public Response buildFormDescriptor(final HostedPaymentPageFieldsJson json, @PathParam("accountId") final String accountIdString, @QueryParam(QUERY_PAYMENT_METHOD_ID) final String paymentMethodIdStr, + @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List paymentControlPluginNames, @QueryParam(QUERY_PLUGIN_PROPERTY) final List pluginPropertiesString, @HeaderParam(HDR_CREATED_BY) final String createdBy, @HeaderParam(HDR_REASON) final String reason, @@ -137,6 +141,7 @@ public Response buildFormDescriptor(final HostedPaymentPageFieldsJson json, @javax.ws.rs.core.Context final UriInfo uriInfo, @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException { final Iterable pluginProperties = extractPluginProperties(pluginPropertiesString); + final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames); final CallContext callContext = context.createContext(createdBy, reason, comment, request); final UUID accountId = UUID.fromString(accountIdString); final Account account = accountUserApi.getAccountById(accountId, callContext); @@ -146,7 +151,7 @@ public Response buildFormDescriptor(final HostedPaymentPageFieldsJson json, final Iterable customFields = extractPluginProperties(json.getCustomFields()); - final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, paymentMethodId, customFields, pluginProperties, callContext); + final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptorWithPaymentControl(account, paymentMethodId, customFields, pluginProperties, paymentOptions, callContext); final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor); return Response.status(Response.Status.OK).entity(result).build(); @@ -161,6 +166,7 @@ public Response buildFormDescriptor(final HostedPaymentPageFieldsJson json, @ApiResponses(value = {}) public Response processNotification(final String body, @PathParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName, + @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List paymentControlPluginNames, @QueryParam(QUERY_PLUGIN_PROPERTY) final List pluginPropertiesString, @HeaderParam(HDR_CREATED_BY) final String createdBy, @HeaderParam(HDR_REASON) final String reason, @@ -168,6 +174,7 @@ public Response processNotification(final String body, @javax.ws.rs.core.Context final UriInfo uriInfo, @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException { final Iterable pluginProperties = extractPluginProperties(pluginPropertiesString); + final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames); final CallContext callContext = context.createContext(createdBy, reason, comment, request); final String notificationPayload; @@ -178,7 +185,7 @@ public Response processNotification(final String body, } // Note: the body is opaque here, as it comes from the gateway. The associated payment plugin will know how to deserialize it though - final GatewayNotification notification = paymentGatewayApi.processNotification(notificationPayload, pluginName, pluginProperties, callContext); + final GatewayNotification notification = paymentGatewayApi.processNotificationWithPaymentControl(notificationPayload, pluginName, pluginProperties, paymentOptions, callContext); final GatewayNotificationJson result = new GatewayNotificationJson(notification); // The plugin told us how to build the response From 2998b9d56ebd33201c3f7f2200688508a2299c4f Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 19 Aug 2015 18:18:36 -0700 Subject: [PATCH 026/137] Add ThreadGroup for payment plugin thread --- .../java/org/killbill/billing/payment/glue/PaymentModule.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java index bd95ba473f..b70276baf0 100644 --- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java +++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java @@ -74,6 +74,7 @@ public class PaymentModule extends KillBillModule { private static final String PLUGIN_THREAD_PREFIX = "Plugin-th-"; + private static final String PAYMENT_PLUGIN_TH_GROUP_NAME = "pay-plugin-grp"; public static final String JANITOR_EXECUTOR_NAMED = "JanitorExecutor"; public static final String PLUGIN_EXECUTOR_NAMED = "PluginExecutor"; @@ -145,7 +146,7 @@ protected void installProcessors(final PaymentConfig paymentConfig) { @Override public Thread newThread(final Runnable r) { - final Thread th = new Thread(r); + final Thread th = new Thread(new ThreadGroup(PAYMENT_PLUGIN_TH_GROUP_NAME), r); th.setName(PLUGIN_THREAD_PREFIX + th.getId()); return th; } From 12ac001b17ec1e70d6bf117b5be294d25cc90ed4 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Thu, 20 Aug 2015 05:16:57 -0700 Subject: [PATCH 027/137] payment: fix NPE if control plugin is not registered This fixes https://github.com/killbill/killbill/issues/379. Signed-off-by: Pierre-Alexandre Meyer --- .../core/sm/control/ControlPluginRunner.java | 2 +- .../sm/control/TestControlPluginRunner.java | 74 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java index 41c37a2f0c..9d037f9407 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java @@ -73,7 +73,7 @@ public PriorPaymentControlResult executePluginPriorCalls(final Account account, final Iterable pluginProperties, final CallContext callContext) throws PaymentControlApiException { // Return as soon as the first plugin aborts, or the last result for the last plugin - PriorPaymentControlResult prevResult = null; + PriorPaymentControlResult prevResult = new DefaultPriorPaymentControlResult(false, amount, currency, paymentMethodId, pluginProperties); // Those values are adjusted prior each call with the result of what previous call to plugin returned Iterable inputPluginProperties = pluginProperties; diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java new file mode 100644 index 0000000000..e48f3b4fdb --- /dev/null +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.payment.core.sm.control; + +import java.math.BigDecimal; +import java.util.UUID; + +import org.killbill.billing.account.api.Account; +import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.control.plugin.api.PaymentApiType; +import org.killbill.billing.control.plugin.api.PriorPaymentControlResult; +import org.killbill.billing.payment.PaymentTestSuiteNoDB; +import org.killbill.billing.payment.api.PluginProperty; +import org.killbill.billing.payment.api.TransactionType; +import org.killbill.billing.payment.provider.DefaultPaymentControlProviderPluginRegistry; +import org.killbill.billing.util.UUIDs; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +public class TestControlPluginRunner extends PaymentTestSuiteNoDB { + + @Test(groups = "fast") + public void testPriorCallWithUnknownPlugin() throws Exception { + final Account account = Mockito.mock(Account.class); + final UUID paymentMethodId = UUIDs.randomUUID(); + final UUID paymentId = UUIDs.randomUUID(); + final String paymentExternalKey = UUIDs.randomUUID().toString(); + final String paymentTransactionExternalKey = UUIDs.randomUUID().toString(); + final BigDecimal amount = BigDecimal.ONE; + final Currency currency = Currency.USD; + final ImmutableList paymentControlPluginNames = ImmutableList.of("not-registered"); + final ImmutableList pluginProperties = ImmutableList.of(); + + final ControlPluginRunner controlPluginRunner = new ControlPluginRunner(new DefaultPaymentControlProviderPluginRegistry()); + final PriorPaymentControlResult paymentControlResult = controlPluginRunner.executePluginPriorCalls(account, + paymentMethodId, + null, + paymentId, + paymentExternalKey, + paymentTransactionExternalKey, + PaymentApiType.PAYMENT_TRANSACTION, + TransactionType.AUTHORIZE, + null, + amount, + currency, + true, + paymentControlPluginNames, + pluginProperties, + callContext); + Assert.assertEquals(paymentControlResult.getAdjustedAmount(), amount); + Assert.assertEquals(paymentControlResult.getAdjustedCurrency(), currency); + Assert.assertEquals(paymentControlResult.getAdjustedPaymentMethodId(), paymentMethodId); + Assert.assertEquals(paymentControlResult.getAdjustedPluginProperties(), pluginProperties); + Assert.assertFalse(paymentControlResult.isAborted()); + } +} From a24ed2be48841427d3e5b04a492b4cc0293bae9e Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Thu, 20 Aug 2015 05:26:34 -0700 Subject: [PATCH 028/137] jaxrs: fix warning on startup This fixes https://github.com/killbill/killbill/issues/381. Signed-off-by: Pierre-Alexandre Meyer --- .../org/killbill/billing/jaxrs/resources/PaymentResource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java index e69e85cf35..804063d190 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java @@ -250,7 +250,6 @@ public Response completeTransaction(final PaymentTransactionJson json, @Timed @PUT - @Path("/") @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @ApiOperation(value = "Complete an existing transaction") From 39a8d61dc6bfd9e97d7e0edce44d82776426eef3 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Thu, 20 Aug 2015 07:18:15 -0700 Subject: [PATCH 029/137] payment: look-up payment by external key if no payment id is passed Add tests to verify the integrity of payment and payment transactions: before, when providing an existing payment external key but no payment id, the code would assume a new payment needed to be created and eventually throw java.sql.SQLIntegrityConstraintViolationException (Duplicate entry 'XXX' for key 'payments_key'). Remove handling of UNKNOWN transactions from PaymentLeavingStateCallback. This should never be called as there is no transition allowed out of an _ERRORED (e.g. AUTH_ERRORED) state. The test testApiRetryWithUnknownPaymentTransaction was passing only because no payment id was set, and so the code assumed to be an INIT state (before bypassing the state machine in PaymentLeavingStateCallback). Now, an exception is correctly thrown (no available transition out of AUTH_ERRORED). This should also help simplify the context (see https://github.com/killbill/killbill/issues/378). This also addresses https://github.com/killbill/killbill/issues/371 by demonstrating we support today multiple PENDING transactions for the same payment (tracked by https://github.com/killbill/killbill/issues/383). Signed-off-by: Pierre-Alexandre Meyer --- .../core/sm/PaymentAutomatonRunner.java | 18 +++- .../payments/PaymentLeavingStateCallback.java | 26 +++--- .../billing/payment/api/TestPaymentApi.java | 86 ++++++++++++++----- 3 files changed, 91 insertions(+), 39 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java index 4353d9e01a..308e6a28a6 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java @@ -125,20 +125,23 @@ public UUID run(final boolean isApiPayment, final TransactionType transactionTyp final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException { final DateTime utcNow = clock.getUTCNow(); - final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, paymentId, transactionId, attemptId, paymentExternalKey, paymentTransactionExternalKey, transactionType, + // Retrieve the payment id from the payment external key if needed + final UUID effectivePaymentId = paymentId != null ? paymentId : retrievePaymentId(paymentExternalKey, internalCallContext); + + final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, effectivePaymentId, transactionId, attemptId, paymentExternalKey, paymentTransactionExternalKey, transactionType, account, paymentMethodId, amount, currency, shouldLockAccount, overridePluginOperationResult, properties, internalCallContext, callContext); final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, utcNow, paymentDao, pluginRegistry, internalCallContext, eventBus, paymentSMHelper); final UUID effectivePaymentMethodId; final String currentStateName; - if (paymentId != null) { + if (effectivePaymentId != null) { final PaymentModelDao paymentModelDao = daoHelper.getPayment(); effectivePaymentMethodId = paymentModelDao.getPaymentMethodId(); currentStateName = paymentModelDao.getLastSuccessStateName() != null ? paymentModelDao.getLastSuccessStateName() : paymentSMHelper.getInitStateNameForTransaction(); // Check for illegal states (should never happen) - Preconditions.checkState(currentStateName != null, "State name cannot be null for payment " + paymentId); + Preconditions.checkState(currentStateName != null, "State name cannot be null for payment " + effectivePaymentId); Preconditions.checkState(paymentMethodId == null || effectivePaymentMethodId.equals(paymentMethodId), "Specified payment method id " + paymentMethodId + " doesn't match the one on the payment " + effectivePaymentMethodId); } else { // If the payment method is not specified, retrieve the default one on the account; it could still be null, in which case @@ -242,4 +245,13 @@ public boolean apply(final PluginProperty input) { return invoiceProperty == null || invoiceProperty.getValue() == null ? null : invoiceProperty.getValue().toString(); } + + private UUID retrievePaymentId(@Nullable final String paymentExternalKey, final InternalCallContext internalCallContext) { + if (paymentExternalKey == null) { + return null; + } + + final PaymentModelDao payment = paymentDao.getPaymentByExternalKey(paymentExternalKey, internalCallContext); + return payment == null ? null : payment.getId(); + } } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java index 6fb17d72f8..edc6f707ed 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java @@ -71,21 +71,12 @@ public void leavingState(final State oldState) throws OperationException { existingPaymentTransactions = ImmutableList.of(); } + // Validate the payment transactions belong to the right payment + validatePaymentId(existingPaymentTransactions); + // Validate some constraints on the unicity of that paymentTransactionExternalKey validateUniqueTransactionExternalKey(existingPaymentTransactions); - // Handle UNKNOWN cases, where we skip the whole state machine and let the getPayment (through Janitor) logic refresh the state. - final PaymentTransactionModelDao unknownPaymentTransaction = getUnknownPaymentTransaction(existingPaymentTransactions); - if (unknownPaymentTransaction != null) { - // Reset the attemptId on the existing paymentTransaction row since it is not accurate - unknownPaymentTransaction.setAttemptId(paymentStateContext.getAttemptId()); - // Set the current paymentTransaction in the context (needed for the state machine logic) - paymentStateContext.setPaymentTransactionModelDao(unknownPaymentTransaction); - // Set special flag to bypass the state machine altogether (plugin will not be called, state will not be updated, no event will be sent unless state is fixed) - paymentStateContext.setSkipOperationForUnknownTransaction(true); - return; - } - // Handle PENDING cases, where we want to re-use the same transaction final PaymentTransactionModelDao pendingPaymentTransaction = getPendingPaymentTransaction(existingPaymentTransactions); if (pendingPaymentTransaction != null) { @@ -94,7 +85,7 @@ public void leavingState(final State oldState) throws OperationException { return; } - // At this point we are left with PAYMENT_FAILURE, PLUGIN_FAILURE or nothing, and we validated the uniquess of the paymentTransactionExternalKey so we will create a new row + // At this point we are left with PAYMENT_FAILURE, PLUGIN_FAILURE or nothing, and we validated the uniqueness of the paymentTransactionExternalKey so we will create a new row daoHelper.createNewPaymentTransaction(); } catch (PaymentApiException e) { @@ -141,4 +132,13 @@ public boolean apply(final PaymentTransactionModelDao input) { throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, paymentStateContext.getPaymentTransactionExternalKey()); } } + + // At this point, the payment id should have been populated for follow-up transactions (see PaymentAutomationRunner#run) + protected void validatePaymentId(final List existingPaymentTransactions) throws PaymentApiException { + for (final PaymentTransactionModelDao paymentTransactionModelDao : existingPaymentTransactions) { + if (!paymentTransactionModelDao.getPaymentId().equals(paymentStateContext.getPaymentId())) { + throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, paymentTransactionModelDao.getId(), "does not belong to payment " + paymentStateContext.getPaymentId()); + } + } + } } diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java index 19c04f6e30..a5b2a5a8f4 100644 --- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java +++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java @@ -582,34 +582,74 @@ public void testInvalidTransitionAfterFailure() throws PaymentApiException { } } - @Test(groups = "slow") - public void testApiRetryWithUnknownPaymentTransaction() throws Exception { + @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/371") + public void testApiWithDuplicatePendingPaymentTransaction() throws Exception { final BigDecimal requestedAmount = BigDecimal.TEN; - final String paymentExternalKey = UUID.randomUUID().toString(); - final String paymentTransactionExternalKey = UUID.randomUUID().toString(); - - final Payment badPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), - paymentExternalKey, paymentTransactionExternalKey, ImmutableList.of(), callContext); - - final String paymentStateName = paymentSMHelper.getErroredStateForTransaction(TransactionType.AUTHORIZE).toString(); - paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), badPayment.getId(), TransactionType.AUTHORIZE, paymentStateName, paymentStateName, - badPayment.getTransactions().get(0).getId(), TransactionStatus.UNKNOWN, requestedAmount, account.getCurrency(), - "eroor 64", "bad something happened", internalCallContext); - - final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), - paymentExternalKey, paymentTransactionExternalKey, ImmutableList.of(), callContext); - - Assert.assertEquals(payment.getId(), badPayment.getId()); - Assert.assertEquals(payment.getExternalKey(), paymentExternalKey); - Assert.assertEquals(payment.getExternalKey(), paymentExternalKey); + for (final TransactionType transactionType : ImmutableList.of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) { + final String payment1ExternalKey = UUID.randomUUID().toString(); + final String payment1TransactionExternalKey = UUID.randomUUID().toString(); + final String payment2ExternalKey = UUID.randomUUID().toString(); + final String payment2TransactionExternalKey = UUID.randomUUID().toString(); + final String payment3TransactionExternalKey = UUID.randomUUID().toString(); + + final Payment pendingPayment1 = createPayment(transactionType, null, payment1ExternalKey, payment1TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING); + Assert.assertNotNull(pendingPayment1); + Assert.assertEquals(pendingPayment1.getExternalKey(), payment1ExternalKey); + Assert.assertEquals(pendingPayment1.getTransactions().size(), 1); + Assert.assertEquals(pendingPayment1.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment1.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment1.getTransactions().get(0).getCurrency(), account.getCurrency()); + Assert.assertEquals(pendingPayment1.getTransactions().get(0).getExternalKey(), payment1TransactionExternalKey); + Assert.assertEquals(pendingPayment1.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING); + + // Attempt to create a second transaction for the same payment, but with a different transaction external key + final Payment pendingPayment2 = createPayment(transactionType, null, payment1ExternalKey, payment2TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING); + Assert.assertNotNull(pendingPayment2); + Assert.assertEquals(pendingPayment2.getId(), pendingPayment1.getId()); + Assert.assertEquals(pendingPayment2.getExternalKey(), payment1ExternalKey); + Assert.assertEquals(pendingPayment2.getTransactions().size(), 2); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getCurrency(), account.getCurrency()); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getExternalKey(), payment1TransactionExternalKey); + Assert.assertEquals(pendingPayment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING); + Assert.assertEquals(pendingPayment2.getTransactions().get(1).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment2.getTransactions().get(1).getProcessedAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment2.getTransactions().get(1).getCurrency(), account.getCurrency()); + Assert.assertEquals(pendingPayment2.getTransactions().get(1).getExternalKey(), payment2TransactionExternalKey); + Assert.assertEquals(pendingPayment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.PENDING); + + try { + // Verify we cannot use the same transaction external key on a different payment if the payment id isn't specified + createPayment(transactionType, null, payment2ExternalKey, payment1TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING); + Assert.fail(); + } catch (final PaymentApiException e) { + Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode()); + } - Assert.assertEquals(payment.getTransactions().size(), 1); - Assert.assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS); - Assert.assertEquals(payment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey); + try { + // Verify we cannot use the same transaction external key on a different payment if the payment id isn't specified + createPayment(transactionType, null, payment2ExternalKey, payment2TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING); + Assert.fail(); + } catch (final PaymentApiException e) { + Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode()); + } + + // Attempt to create a second transaction for a different payment + final Payment pendingPayment3 = createPayment(transactionType, null, payment2ExternalKey, payment3TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING); + Assert.assertNotNull(pendingPayment3); + Assert.assertNotEquals(pendingPayment3.getId(), pendingPayment1.getId()); + Assert.assertEquals(pendingPayment3.getExternalKey(), payment2ExternalKey); + Assert.assertEquals(pendingPayment3.getTransactions().size(), 1); + Assert.assertEquals(pendingPayment3.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment3.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); + Assert.assertEquals(pendingPayment3.getTransactions().get(0).getCurrency(), account.getCurrency()); + Assert.assertEquals(pendingPayment3.getTransactions().get(0).getExternalKey(), payment3TransactionExternalKey); + Assert.assertEquals(pendingPayment3.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING); + } } - // Example of a 3D secure payment for instance @Test(groups = "slow") public void testApiWithPendingPaymentTransaction() throws Exception { for (final TransactionType transactionType : ImmutableList.of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) { From f20b73bf887a1159cee0878e8a46548355bd6ddb Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 20 Aug 2015 17:13:22 -0700 Subject: [PATCH 030/137] Fixes #355 --- .../payment/core/PaymentExecutors.java | 99 +++++++++++++++++++ .../payment/core/PaymentGatewayProcessor.java | 16 +-- .../payment/core/PaymentMethodProcessor.java | 11 +-- .../payment/core/PaymentProcessor.java | 6 +- .../core/PluginControlPaymentProcessor.java | 7 +- .../billing/payment/core/ProcessorBase.java | 10 +- .../billing/payment/core/janitor/Janitor.java | 20 ++-- .../core/sm/PaymentAutomatonRunner.java | 14 +-- .../PluginControlPaymentAutomatonRunner.java | 13 +-- .../payment/dispatcher/PluginDispatcher.java | 12 ++- .../payment/glue/DefaultPaymentService.java | 13 ++- .../billing/payment/glue/PaymentModule.java | 34 +------ .../billing/payment/PaymentTestSuiteNoDB.java | 3 + .../PaymentTestSuiteWithEmbeddedDB.java | 3 + .../killbill/billing/payment/TestJanitor.java | 2 + .../MockRetryablePaymentAutomatonRunner.java | 13 +-- .../payment/core/sm/TestPaymentOperation.java | 3 +- .../payment/core/sm/TestPluginOperation.java | 2 +- .../payment/core/sm/TestRetryablePayment.java | 37 +++---- .../dispatcher/TestPluginDispatcher.java | 4 +- 20 files changed, 185 insertions(+), 137 deletions(-) create mode 100644 payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java new file mode 100644 index 0000000000..bd4ca08499 --- /dev/null +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java @@ -0,0 +1,99 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.payment.core; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; + +import org.killbill.billing.util.config.PaymentConfig; +import org.killbill.commons.concurrent.Executors; +import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor; + +public class PaymentExecutors { + + private static final long TIMEOUT_EXECUTOR_SEC = 3L; + + private static final String PLUGIN_THREAD_PREFIX = "Plugin-th-"; + private static final String PAYMENT_PLUGIN_TH_GROUP_NAME = "pay-plugin-grp"; + + public static final String JANITOR_EXECUTOR_NAMED = "JanitorExecutor"; + public static final String PLUGIN_EXECUTOR_NAMED = "PluginExecutor"; + + private final PaymentConfig paymentConfig; + + private volatile ExecutorService pluginExecutorService; + private volatile ScheduledExecutorService janitorExecutorService; + + @Inject + public PaymentExecutors(PaymentConfig paymentConfig) { + this.paymentConfig = paymentConfig; + + } + + public void initialize() { + this.pluginExecutorService = createPluginExecutorService(); + this.janitorExecutorService = createJanitorExecutorService(); + } + + + public void stop() throws InterruptedException { + pluginExecutorService.shutdownNow(); + janitorExecutorService.shutdownNow(); + + pluginExecutorService.awaitTermination(TIMEOUT_EXECUTOR_SEC, TimeUnit.SECONDS); + pluginExecutorService = null; + + janitorExecutorService.awaitTermination(TIMEOUT_EXECUTOR_SEC, TimeUnit.SECONDS); + janitorExecutorService = null; + } + + public ExecutorService getPluginExecutorService() { + return pluginExecutorService; + } + + public ScheduledExecutorService getJanitorExecutorService() { + return janitorExecutorService; + } + + private ExecutorService createPluginExecutorService() { + return new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(), + paymentConfig.getPaymentPluginThreadNb(), + 0L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(), + new ThreadFactory() { + + @Override + public Thread newThread(final Runnable r) { + final Thread th = new Thread(new ThreadGroup(PAYMENT_PLUGIN_TH_GROUP_NAME), r); + th.setName(PLUGIN_THREAD_PREFIX + th.getId()); + return th; + } + }); + + } + + private ScheduledExecutorService createJanitorExecutorService() { + return Executors.newSingleThreadScheduledExecutor("PaymentJanitor"); + } +} diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java index dbc75412be..cf9e41fe9d 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java @@ -19,7 +19,6 @@ import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -47,13 +46,8 @@ import org.killbill.billing.util.config.PaymentConfig; import org.killbill.clock.Clock; import org.killbill.commons.locker.GlobalLocker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.common.base.Objects; -import com.google.inject.name.Named; - -import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED; // We don't take any lock here because the call needs to be re-entrant // from the plugin: for example, the BitPay plugin will create the payment during the @@ -65,8 +59,6 @@ public class PaymentGatewayProcessor extends ProcessorBase { private final PluginDispatcher paymentPluginFormDispatcher; private final PluginDispatcher paymentPluginNotificationDispatcher; - private static final Logger log = LoggerFactory.getLogger(PaymentGatewayProcessor.class); - @Inject public PaymentGatewayProcessor(final OSGIServiceRegistration pluginRegistry, final AccountInternalApi accountUserApi, @@ -75,13 +67,13 @@ public PaymentGatewayProcessor(final OSGIServiceRegistration p final PaymentDao paymentDao, final GlobalLocker locker, final PaymentConfig paymentConfig, - @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, + final PaymentExecutors executors, final InternalCallContextFactory internalCallContextFactory, final Clock clock) { - super(pluginRegistry, accountUserApi, paymentDao, tagUserApi, locker, executor, internalCallContextFactory, invoiceApi, clock); + super(pluginRegistry, accountUserApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock); final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit()); - this.paymentPluginFormDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executor); - this.paymentPluginNotificationDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executor); + this.paymentPluginFormDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executors); + this.paymentPluginNotificationDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executors); } public GatewayNotification processNotification(final String notification, final String pluginName, final Iterable properties, final CallContext callContext) throws PaymentApiException { diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java index 4babea1d07..102c3d2d31 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -52,6 +51,7 @@ import org.killbill.billing.payment.provider.DefaultPaymentMethodInfoPlugin; import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin; import org.killbill.billing.tag.TagInternalApi; +import org.killbill.billing.util.UUIDs; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.InternalCallContextFactory; import org.killbill.billing.util.callcontext.TenantContext; @@ -69,10 +69,7 @@ import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; -import com.google.inject.name.Named; -import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED; -import org.killbill.billing.util.UUIDs; import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination; import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins; @@ -90,12 +87,12 @@ public PaymentMethodProcessor(final OSGIServiceRegistration pl final TagInternalApi tagUserApi, final GlobalLocker locker, final PaymentConfig paymentConfig, - @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, + final PaymentExecutors executors, final InternalCallContextFactory internalCallContextFactory, final Clock clock) { - super(pluginRegistry, accountInternalApi, paymentDao, tagUserApi, locker, executor, internalCallContextFactory, invoiceApi, clock); + super(pluginRegistry, accountInternalApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock); final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit()); - this.uuidPluginNotificationDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executor); + this.uuidPluginNotificationDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executors); } public UUID addPaymentMethod(final String paymentMethodExternalKey, final String paymentPluginServiceName, final Account account, diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java index 3326bd30d6..7b39cf1b64 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; import javax.inject.Inject; @@ -75,9 +74,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; -import com.google.inject.name.Named; -import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED; import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination; import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins; @@ -98,11 +95,10 @@ public PaymentProcessor(final OSGIServiceRegistration pluginRe final PaymentDao paymentDao, final InternalCallContextFactory internalCallContextFactory, final GlobalLocker locker, - @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentAutomatonRunner paymentAutomatonRunner, final IncompletePaymentTransactionTask incompletePaymentTransactionTask, final Clock clock) { - super(pluginRegistry, accountUserApi, paymentDao, tagUserApi, locker, executor, internalCallContextFactory, invoiceApi, clock); + super(pluginRegistry, accountUserApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock); this.paymentAutomatonRunner = paymentAutomatonRunner; this.incompletePaymentTransactionTask = incompletePaymentTransactionTask; } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java index fd66fe91fe..96a6eda749 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java @@ -21,7 +21,6 @@ import java.util.Collection; import java.util.List; import java.util.UUID; -import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; import javax.inject.Inject; @@ -55,9 +54,6 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import com.google.inject.name.Named; - -import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED; public class PluginControlPaymentProcessor extends ProcessorBase { @@ -73,12 +69,11 @@ public PluginControlPaymentProcessor(final OSGIServiceRegistration pluginRegistry; protected final AccountInternalApi accountInternalApi; protected final GlobalLocker locker; - protected final ExecutorService executor; protected final PaymentDao paymentDao; protected final InternalCallContextFactory internalCallContextFactory; protected final TagInternalApi tagInternalApi; @@ -88,7 +81,6 @@ public ProcessorBase(final OSGIServiceRegistration pluginRegis final PaymentDao paymentDao, final TagInternalApi tagInternalApi, final GlobalLocker locker, - final ExecutorService executor, final InternalCallContextFactory internalCallContextFactory, final InvoiceInternalApi invoiceApi, final Clock clock) { @@ -96,7 +88,6 @@ public ProcessorBase(final OSGIServiceRegistration pluginRegis this.accountInternalApi = accountInternalApi; this.paymentDao = paymentDao; this.locker = locker; - this.executor = executor; this.tagInternalApi = tagInternalApi; this.internalCallContextFactory = internalCallContextFactory; this.invoiceApi = invoiceApi; @@ -168,6 +159,7 @@ protected CallContext buildCallContext(final InternalCallContext context) { // TODO Rename - there is no lock! public interface WithAccountLockCallback { + public PluginDispatcherReturnType doOperation() throws ExceptionType; } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java index 3b06de11b6..2c21d9c4dd 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java @@ -22,12 +22,11 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; -import javax.inject.Named; import org.joda.time.DateTime; import org.killbill.billing.events.PaymentInternalEvent; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.glue.DefaultPaymentService; -import org.killbill.billing.payment.glue.PaymentModule; import org.killbill.billing.util.config.PaymentConfig; import org.killbill.notificationq.api.NotificationEvent; import org.killbill.notificationq.api.NotificationQueue; @@ -49,27 +48,28 @@ public class Janitor { public static final String QUEUE_NAME = "janitor"; private final NotificationQueueService notificationQueueService; - private final ScheduledExecutorService janitorExecutor; private final PaymentConfig paymentConfig; private final IncompletePaymentAttemptTask incompletePaymentAttemptTask; private final IncompletePaymentTransactionTask incompletePaymentTransactionTask; + private final PaymentExecutors paymentExecutors; private NotificationQueue janitorQueue; + private ScheduledExecutorService janitorExecutor; private volatile boolean isStopped; @Inject public Janitor(final PaymentConfig paymentConfig, final NotificationQueueService notificationQueueService, - @Named(PaymentModule.JANITOR_EXECUTOR_NAMED) final ScheduledExecutorService janitorExecutor, + final PaymentExecutors paymentExecutors, final IncompletePaymentAttemptTask incompletePaymentAttemptTask, final IncompletePaymentTransactionTask incompletePaymentTransactionTask) { this.notificationQueueService = notificationQueueService; - this.janitorExecutor = janitorExecutor; + this.paymentExecutors = paymentExecutors; this.paymentConfig = paymentConfig; this.incompletePaymentAttemptTask = incompletePaymentAttemptTask; this.incompletePaymentTransactionTask = incompletePaymentTransactionTask; - this.isStopped = false; + } public void initialize() throws NotificationQueueAlreadyExists { @@ -95,10 +95,10 @@ public void handleReadyNotification(final NotificationEvent notificationKey, fin } public void start() { - if (isStopped) { - log.warn("Janitor is not a restartable service, and was already started, aborting"); - return; - } + + this.isStopped = false; + + janitorExecutor = paymentExecutors.getJanitorExecutorService(); janitorQueue.startQueue(); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java index 308e6a28a6..d0f56a03c7 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java @@ -19,7 +19,6 @@ import java.math.BigDecimal; import java.util.UUID; -import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -36,7 +35,6 @@ import org.killbill.automaton.State.EnteringStateCallback; import org.killbill.automaton.State.LeavingStateCallback; import org.killbill.automaton.StateMachine; -import org.killbill.automaton.StateMachineConfig; import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalCallContext; @@ -45,6 +43,7 @@ import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionType; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.core.sm.payments.AuthorizeCompleted; import org.killbill.billing.payment.core.sm.payments.AuthorizeInitiated; import org.killbill.billing.payment.core.sm.payments.AuthorizeOperation; @@ -69,7 +68,6 @@ import org.killbill.billing.payment.dao.PaymentDao; import org.killbill.billing.payment.dao.PaymentModelDao; import org.killbill.billing.payment.dispatcher.PluginDispatcher; -import org.killbill.billing.payment.glue.PaymentModule; import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi; import org.killbill.billing.payment.plugin.api.PaymentPluginApi; import org.killbill.billing.util.callcontext.CallContext; @@ -82,9 +80,6 @@ import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; -import com.google.inject.name.Named; - -import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED; public class PaymentAutomatonRunner { @@ -97,13 +92,12 @@ public class PaymentAutomatonRunner { private final PersistentBus eventBus; @Inject - public PaymentAutomatonRunner(@javax.inject.Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, - final PaymentConfig paymentConfig, + public PaymentAutomatonRunner(final PaymentConfig paymentConfig, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration pluginRegistry, final Clock clock, - @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, + final PaymentExecutors executors, final PersistentBus eventBus, final PaymentStateMachineHelper paymentSMHelper) { this.paymentSMHelper = paymentSMHelper; @@ -114,7 +108,7 @@ public PaymentAutomatonRunner(@javax.inject.Named(PaymentModule.STATE_MACHINE_PA this.eventBus = eventBus; final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit()); - this.paymentPluginDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executor); + this.paymentPluginDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executors); } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java index f3ca0cad79..4f58817507 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.util.List; import java.util.UUID; -import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; import javax.inject.Inject; @@ -32,16 +31,17 @@ import org.killbill.automaton.State; import org.killbill.automaton.State.EnteringStateCallback; import org.killbill.automaton.State.LeavingStateCallback; -import org.killbill.automaton.StateMachineConfig; import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.Payment; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionType; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.sm.control.AuthorizeControlOperation; import org.killbill.billing.payment.core.sm.control.CaptureControlOperation; @@ -57,10 +57,8 @@ import org.killbill.billing.payment.core.sm.control.RefundControlOperation; import org.killbill.billing.payment.core.sm.control.VoidControlOperation; import org.killbill.billing.payment.dao.PaymentDao; -import org.killbill.billing.payment.glue.PaymentModule; import org.killbill.billing.payment.plugin.api.PaymentPluginApi; import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler; -import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.config.PaymentConfig; import org.killbill.bus.api.PersistentBus; @@ -70,7 +68,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; -import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED; import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED; public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner { @@ -82,11 +79,11 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner private final ControlPluginRunner controlPluginRunner; @Inject - public PluginControlPaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration pluginRegistry, + public PluginControlPaymentAutomatonRunner(final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration pluginRegistry, final OSGIServiceRegistration paymentControlPluginRegistry, final Clock clock, final PaymentProcessor paymentProcessor, @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, - final PaymentConfig paymentConfig, @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper, + final PaymentConfig paymentConfig, final PaymentExecutors executors, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper, final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) { - super(stateMachineConfig, paymentConfig, paymentDao, locker, pluginRegistry, clock, executor, eventBus, paymentSMHelper); + super(paymentConfig, paymentDao, locker, pluginRegistry, clock, executors, eventBus, paymentSMHelper); this.paymentProcessor = paymentProcessor; this.paymentControlPluginRegistry = paymentControlPluginRegistry; this.retryServiceScheduler = retryServiceScheduler; diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java index f8b6ca511a..bda52e0e5f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java +++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java @@ -20,11 +20,13 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.commons.profiling.Profiling; import org.killbill.commons.profiling.ProfilingData; import org.killbill.commons.request.Request; @@ -34,11 +36,11 @@ public class PluginDispatcher { private final TimeUnit DEEFAULT_PLUGIN_TIMEOUT_UNIT = TimeUnit.SECONDS; private final long timeoutSeconds; - private final ExecutorService executor; + private final PaymentExecutors paymentExecutors; - public PluginDispatcher(final long timeoutSeconds, final ExecutorService executor) { + public PluginDispatcher(final long timeoutSeconds, final PaymentExecutors paymentExecutors) { this.timeoutSeconds = timeoutSeconds; - this.executor = executor; + this.paymentExecutors = paymentExecutors; } // TODO Once we switch fully to automata, should this throw PaymentPluginApiException instead? @@ -49,10 +51,12 @@ public ReturnType dispatchWithTimeout(final Callable> task, final long timeout, final TimeUnit unit) throws TimeoutException, ExecutionException, InterruptedException { + final ExecutorService pluginExecutor = paymentExecutors.getPluginExecutorService(); + // Wrap existing callable to keep the original requestId final Callable> callableWithRequestData = new CallableWithRequestData(Request.getPerThreadRequestData(), task); - final Future> future = executor.submit(callableWithRequestData); + final Future> future = pluginExecutor.submit(callableWithRequestData); final PluginDispatcherReturnType pluginDispatcherResult = future.get(timeout, unit); if (pluginDispatcherResult instanceof WithProfilingPluginDispatcherReturnType) { diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java index d003f743a0..93603b1d0f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java +++ b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java @@ -21,6 +21,7 @@ import org.killbill.billing.payment.api.PaymentApi; import org.killbill.billing.payment.api.PaymentService; import org.killbill.billing.payment.bus.PaymentBusEventHandler; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.invoice.PaymentTagHandler; import org.killbill.billing.payment.core.janitor.Janitor; import org.killbill.billing.payment.retry.DefaultRetryService; @@ -46,6 +47,7 @@ public class DefaultPaymentService implements PaymentService { private final PaymentApi api; private final DefaultRetryService retryService; private final Janitor janitor; + private final PaymentExecutors paymentExecutors; @Inject public DefaultPaymentService(final PaymentBusEventHandler paymentBusEventHandler, @@ -53,13 +55,15 @@ public DefaultPaymentService(final PaymentBusEventHandler paymentBusEventHandler final PaymentApi api, final DefaultRetryService retryService, final PersistentBus eventBus, - final Janitor janitor) { + final Janitor janitor, + final PaymentExecutors paymentExecutors) { this.paymentBusEventHandler = paymentBusEventHandler; this.tagHandler = tagHandler; this.eventBus = eventBus; this.api = api; this.retryService = retryService; this.janitor = janitor; + this.paymentExecutors = paymentExecutors; } @Override @@ -75,6 +79,7 @@ public void initialize() throws NotificationQueueAlreadyExists { } catch (final PersistentBus.EventBusException e) { log.error("Unable to register with the EventBus!", e); } + paymentExecutors.initialize(); retryService.initialize(); janitor.initialize(); } @@ -95,6 +100,12 @@ public void stop() throws NoSuchNotificationQueue { } retryService.stop(); janitor.stop(); + try { + paymentExecutors.stop(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("PaymentService got interrupted", e); + } } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java index b70276baf0..b249c8b49f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java +++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java @@ -18,16 +18,11 @@ package org.killbill.billing.payment.glue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - import javax.inject.Provider; import org.killbill.automaton.DefaultStateMachineConfig; import org.killbill.automaton.StateMachineConfig; +import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.AdminPaymentApi; import org.killbill.billing.payment.api.DefaultAdminPaymentApi; @@ -37,6 +32,7 @@ import org.killbill.billing.payment.api.PaymentGatewayApi; import org.killbill.billing.payment.api.PaymentService; import org.killbill.billing.payment.bus.PaymentBusEventHandler; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.core.PaymentGatewayProcessor; import org.killbill.billing.payment.core.PaymentMethodProcessor; import org.killbill.billing.payment.core.PaymentProcessor; @@ -58,10 +54,8 @@ import org.killbill.billing.payment.retry.DefaultRetryService.DefaultRetryServiceScheduler; import org.killbill.billing.payment.retry.RetryService; import org.killbill.billing.platform.api.KillbillConfigSource; -import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.util.config.PaymentConfig; import org.killbill.billing.util.glue.KillBillModule; -import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor; import org.killbill.xmlloader.XMLLoader; import org.skife.config.ConfigurationObjectFactory; @@ -73,11 +67,7 @@ public class PaymentModule extends KillBillModule { - private static final String PLUGIN_THREAD_PREFIX = "Plugin-th-"; - private static final String PAYMENT_PLUGIN_TH_GROUP_NAME = "pay-plugin-grp"; - public static final String JANITOR_EXECUTOR_NAMED = "JanitorExecutor"; - public static final String PLUGIN_EXECUTOR_NAMED = "PluginExecutor"; public static final String RETRYABLE_NAMED = "Retryable"; public static final String STATE_MACHINE_RETRY = "RetryStateMachine"; @@ -102,9 +92,6 @@ protected void installPaymentProviderPlugins(final PaymentConfig config) { } protected void installJanitor() { - final ScheduledExecutorService janitorExecutor = org.killbill.commons.concurrent.Executors.newSingleThreadScheduledExecutor("PaymentJanitor"); - bind(ScheduledExecutorService.class).annotatedWith(Names.named(JANITOR_EXECUTOR_NAMED)).toInstance(janitorExecutor); - bind(IncompletePaymentTransactionTask.class).asEagerSingleton(); bind(IncompletePaymentAttemptTask.class).asEagerSingleton(); bind(Janitor.class).asEagerSingleton(); @@ -136,22 +123,6 @@ protected void installAutomatonRunner() { } protected void installProcessors(final PaymentConfig paymentConfig) { - - final ExecutorService pluginExecutorService = new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(), - paymentConfig.getPaymentPluginThreadNb(), - 0L, - TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - new ThreadFactory() { - - @Override - public Thread newThread(final Runnable r) { - final Thread th = new Thread(new ThreadGroup(PAYMENT_PLUGIN_TH_GROUP_NAME), r); - th.setName(PLUGIN_THREAD_PREFIX + th.getId()); - return th; - } - }); - bind(ExecutorService.class).annotatedWith(Names.named(PLUGIN_EXECUTOR_NAMED)).toInstance(pluginExecutorService); bind(PaymentProcessor.class).asEagerSingleton(); bind(PluginControlPaymentProcessor.class).asEagerSingleton(); bind(PaymentGatewayProcessor.class).asEagerSingleton(); @@ -173,6 +144,7 @@ protected void configure() { bind(PaymentBusEventHandler.class).asEagerSingleton(); bind(PaymentTagHandler.class).asEagerSingleton(); bind(PaymentService.class).to(DefaultPaymentService.class).asEagerSingleton(); + bind(PaymentExecutors.class).asEagerSingleton(); installPaymentProviderPlugins(paymentConfig); installPaymentDao(); installProcessors(paymentConfig); diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java index 9c19560c6c..8c45a5a8d1 100644 --- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java +++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java @@ -24,6 +24,7 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.PaymentApi; import org.killbill.billing.payment.api.PaymentGatewayApi; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.core.PaymentMethodProcessor; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.PluginControlPaymentProcessor; @@ -82,6 +83,8 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB { protected DefaultRetryService retryService; @Inject protected CacheControllerDispatcher cacheControllerDispatcher; + @Inject + protected PaymentExecutors paymentExecutors; @Override protected KillbillConfigSource getConfigSource() { diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java index 1ab3f64aa5..aefb209af8 100644 --- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java +++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java @@ -24,6 +24,7 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.PaymentApi; import org.killbill.billing.payment.api.PaymentGatewayApi; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.PaymentMethodProcessor; import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper; @@ -70,6 +71,8 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu protected PaymentDao paymentDao; @Inject protected TestPaymentHelper testHelper; + @Inject + protected PaymentExecutors paymentExecutors; @Override protected KillbillConfigSource getConfigSource() { diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java index 7ed69f5416..8303dc6ad1 100644 --- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java +++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java @@ -114,6 +114,7 @@ protected KillbillConfigSource getConfigSource() { protected void beforeClass() throws Exception { super.beforeClass(); mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME); + paymentExecutors.initialize(); janitor.initialize(); janitor.start(); } @@ -121,6 +122,7 @@ protected void beforeClass() throws Exception { @AfterClass(groups = "slow") protected void afterClass() throws Exception { janitor.stop(); + paymentExecutors.stop(); } @BeforeMethod(groups = "slow") diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java index 14f6758796..07c8031c0e 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.util.List; import java.util.UUID; -import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; import javax.inject.Inject; @@ -28,23 +27,22 @@ import org.killbill.automaton.Operation.OperationCallback; import org.killbill.automaton.OperationResult; -import org.killbill.automaton.StateMachineConfig; import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionType; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.sm.control.ControlPluginRunner; import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext; import org.killbill.billing.payment.dao.PaymentDao; import org.killbill.billing.payment.dispatcher.PluginDispatcher; -import org.killbill.billing.payment.glue.PaymentModule; import org.killbill.billing.payment.plugin.api.PaymentPluginApi; import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler; -import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.tag.TagInternalApi; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.config.PaymentConfig; @@ -52,7 +50,6 @@ import org.killbill.clock.Clock; import org.killbill.commons.locker.GlobalLocker; -import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED; import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED; public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAutomatonRunner { @@ -61,10 +58,10 @@ public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAut private PaymentStateControlContext context; @Inject - public MockRetryablePaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, @Named(PaymentModule.STATE_MACHINE_RETRY) final StateMachineConfig retryStateMachine, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration pluginRegistry, final OSGIServiceRegistration retryPluginRegistry, final Clock clock, final TagInternalApi tagApi, final PaymentProcessor paymentProcessor, - @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, + public MockRetryablePaymentAutomatonRunner(final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration pluginRegistry, final OSGIServiceRegistration retryPluginRegistry, final Clock clock, final TagInternalApi tagApi, final PaymentProcessor paymentProcessor, + @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, final PaymentConfig paymentConfig, final PaymentExecutors executors, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper retrySMHelper, final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) { - super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, controlPluginRunner, eventBus); + super(paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executors, paymentSMHelper, retrySMHelper, controlPluginRunner, eventBus); } @Override diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java index 586e7597f2..7e4e1b63c9 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java @@ -19,7 +19,6 @@ import java.math.BigDecimal; import java.util.UUID; -import java.util.concurrent.Executors; import javax.annotation.Nullable; @@ -104,7 +103,7 @@ public void testPaymentSuccess() throws Exception { private void setUp(final PaymentPluginStatus paymentPluginStatus) throws Exception { final GlobalLocker locker = new MemoryGlobalLocker(); - final PluginDispatcher paymentPluginDispatcher = new PluginDispatcher(1, Executors.newCachedThreadPool()); + final PluginDispatcher paymentPluginDispatcher = new PluginDispatcher(1, paymentExecutors); paymentStateContext = new PaymentStateContext(true, UUID.randomUUID(), null, null, diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java index f004f02003..9c73489543 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java @@ -184,7 +184,7 @@ private PaymentOperation getPluginOperation(final boolean shouldLockAccount) thr } private PaymentOperation getPluginOperation(final boolean shouldLockAccount, final int timeoutSeconds) throws PaymentApiException { - final PluginDispatcher paymentPluginDispatcher = new PluginDispatcher(timeoutSeconds, Executors.newCachedThreadPool()); + final PluginDispatcher paymentPluginDispatcher = new PluginDispatcher(timeoutSeconds, paymentExecutors); final PaymentStateContext paymentStateContext = new PaymentStateContext(true, UUID.randomUUID(), null, null, diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java index 433806e98d..b79660746d 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.util.List; import java.util.UUID; -import java.util.concurrent.ExecutorService; import javax.inject.Named; @@ -32,6 +31,7 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.osgi.api.OSGIServiceDescriptor; import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.PaymentTestSuiteNoDB; @@ -39,6 +39,7 @@ import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionStatus; import org.killbill.billing.payment.api.TransactionType; +import org.killbill.billing.payment.core.PaymentExecutors; import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.core.PluginControlPaymentProcessor; import org.killbill.billing.payment.core.sm.control.ControlPluginRunner; @@ -53,7 +54,6 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginApi; import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin; import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler; -import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.tag.TagInternalApi; import org.killbill.billing.util.callcontext.InternalCallContextFactory; import org.killbill.billing.util.dao.NonEntityDao; @@ -72,7 +72,6 @@ import com.google.common.collect.Iterables; import com.google.inject.Inject; -import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED; import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; @@ -103,8 +102,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB { @Named(RETRYABLE_NAMED) private RetryServiceScheduler retryServiceScheduler; @Inject - @Named(PLUGIN_EXECUTOR_NAMED) - private ExecutorService executor; + private PaymentExecutors executors; @Inject private PaymentStateMachineHelper paymentSMHelper; @Inject @@ -158,8 +156,6 @@ public void beforeMethod() throws Exception { this.utcNow = clock.getUTCNow(); runner = new MockRetryablePaymentAutomatonRunner( - stateMachineConfig, - retryStateMachineConfig, paymentDao, locker, pluginRegistry, @@ -169,7 +165,7 @@ public void beforeMethod() throws Exception { paymentProcessor, retryServiceScheduler, paymentConfig, - executor, + paymentExecutors, paymentSMHelper, retrySMHelper, controlPluginRunner, @@ -177,18 +173,18 @@ public void beforeMethod() throws Exception { paymentStateContext = new PaymentStateControlContext(ImmutableList.of(MockPaymentControlProviderPlugin.PLUGIN_NAME), - true, - null, - paymentExternalKey, - paymentTransactionExternalKey, - TransactionType.AUTHORIZE, - account, - paymentMethodId, - amount, - currency, - emptyProperties, - internalCallContext, - callContext); + true, + null, + paymentExternalKey, + paymentTransactionExternalKey, + TransactionType.AUTHORIZE, + account, + paymentMethodId, + amount, + currency, + emptyProperties, + internalCallContext, + callContext); mockRetryAuthorizeOperationCallback = new MockRetryAuthorizeOperationCallback(locker, @@ -205,7 +201,6 @@ public void beforeMethod() throws Exception { tagApi, paymentDao, locker, - executor, internalCallContextFactory, runner, retrySMHelper, diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java index aef5eec41a..86365e5863 100644 --- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java +++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java @@ -33,9 +33,9 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB { - private final PluginDispatcher voidPluginDispatcher = new PluginDispatcher(10, Executors.newSingleThreadExecutor()); + private final PluginDispatcher voidPluginDispatcher = new PluginDispatcher(10, paymentExecutors); - private final PluginDispatcher stringPluginDispatcher = new PluginDispatcher(1, Executors.newSingleThreadExecutor()); + private final PluginDispatcher stringPluginDispatcher = new PluginDispatcher(1, paymentExecutors); @Test(groups = "fast") public void testDispatchWithTimeout() throws TimeoutException, PaymentApiException { From 08211b5dec03682177bc190e0362ebd4d385887d Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 20 Aug 2015 17:14:39 -0700 Subject: [PATCH 031/137] Rename RequestIdFilter -> RequestDataFilter --- .../filters/{RequestIdFilter.java => RequestDataFilter.java} | 2 +- .../billing/server/listeners/KillbillGuiceListener.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename profiles/killbill/src/main/java/org/killbill/billing/server/filters/{RequestIdFilter.java => RequestDataFilter.java} (95%) diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java similarity index 95% rename from profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java rename to profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java index 3cfe141ca0..a32a507291 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java @@ -30,7 +30,7 @@ import com.sun.jersey.spi.container.ContainerResponseFilter; @Singleton -public class RequestIdFilter implements ContainerRequestFilter, ContainerResponseFilter { +public class RequestDataFilter implements ContainerRequestFilter, ContainerResponseFilter { private static final String REQUEST_ID_HEADER_REQ = "X-Killbill-Request-Id-Req"; diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java index bca802ad93..0b16384771 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java @@ -28,7 +28,7 @@ import org.killbill.billing.platform.api.KillbillConfigSource; import org.killbill.billing.platform.config.DefaultKillbillConfigSource; import org.killbill.billing.server.filters.ProfilingContainerResponseFilter; -import org.killbill.billing.server.filters.RequestIdFilter; +import org.killbill.billing.server.filters.RequestDataFilter; import org.killbill.billing.server.filters.ResponseCorsFilter; import org.killbill.billing.server.modules.KillbillServerModule; import org.killbill.billing.server.security.TenantFilter; @@ -74,7 +74,7 @@ protected ServletModule getServletModule() { // The logging filter is still incompatible with the GZIP filter //builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName()); builder.addJerseyFilter(ProfilingContainerResponseFilter.class.getName()); - builder.addJerseyFilter(RequestIdFilter.class.getName()); + builder.addJerseyFilter(RequestDataFilter.class.getName()); // Broader, to support the "Try it out!" feature //builder.addFilter("/" + SWAGGER_PATH + "*", ResponseCorsFilter.class); From c974e1962803dc71dd4d7406df143e4ba9da2a4d Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 20 Aug 2015 17:15:16 -0700 Subject: [PATCH 032/137] Remove unwanted test check file --- .../org/killbill/billing/jaxrs/TestRL.java | 321 ------------------ 1 file changed, 321 deletions(-) delete mode 100644 profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java deleted file mode 100644 index 11e19dec0b..0000000000 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC - * - * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs; - -import java.math.BigDecimal; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -import javax.annotation.Nullable; - -import org.killbill.billing.ErrorCode; -import org.killbill.billing.ObjectType; -import org.killbill.billing.client.KillBillClientException; -import org.killbill.billing.client.model.Account; -import org.killbill.billing.client.model.Accounts; -import org.killbill.billing.client.model.AuditLog; -import org.killbill.billing.client.model.CustomField; -import org.killbill.billing.client.model.InvoicePayments; -import org.killbill.billing.client.model.PaymentMethod; -import org.killbill.billing.client.model.PaymentMethodPluginDetail; -import org.killbill.billing.client.model.Tag; -import org.killbill.billing.util.api.AuditLevel; -import org.testng.Assert; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -public class TestRL extends TestJaxrsBase { - - @Test(groups = "slow", description = "Verify no PII data is required") - public void testEmptyAccount() throws Exception { - final Account emptyAccount = new Account(); - - final Account account = killBillClient.createAccount(emptyAccount, createdBy, reason, comment); - Assert.assertNotNull(account.getExternalKey()); - Assert.assertNull(account.getName()); - Assert.assertNull(account.getEmail()); - } - - @Test(groups = "slow", description = "Verify external key is unique") - public void testUniqueExternalKey() throws Exception { - // Verify the external key is not mandatory - final Account inputWithNoExternalKey = getAccount(UUID.randomUUID().toString(), null, UUID.randomUUID().toString()); - Assert.assertNull(inputWithNoExternalKey.getExternalKey()); - - final Account account = killBillClient.createAccount(inputWithNoExternalKey, createdBy, reason, comment); - Assert.assertNotNull(account.getExternalKey()); - - final Account inputWithSameExternalKey = getAccount(UUID.randomUUID().toString(), account.getExternalKey(), UUID.randomUUID().toString()); - try { - killBillClient.createAccount(inputWithSameExternalKey, createdBy, reason, comment); - Assert.fail(); - } catch (final KillBillClientException e) { - Assert.assertEquals(e.getBillingException().getCode(), (Integer) ErrorCode.ACCOUNT_ALREADY_EXISTS.getCode()); - } - } - - @Test(groups = "slow", description = "Can create, retrieve, search and update accounts") - public void testAccountOk() throws Exception { - final Account input = createAccount(); - - // Retrieves by external key - final Account retrievedAccount = killBillClient.getAccount(input.getExternalKey()); - Assert.assertTrue(retrievedAccount.equals(input)); - - // Try search endpoint - searchAccount(input, retrievedAccount); - - // Update Account - final Account newInput = new Account(input.getAccountId(), - "zozo", 4, input.getExternalKey(), "rr@google.com", 18, - "USD", null, "UTC", "bl1", "bh2", "", "", "ca", "San Francisco", "usa", "en", "415-255-2991", - false, false, null, null); - final Account updatedAccount = killBillClient.updateAccount(newInput, createdBy, reason, comment); - Assert.assertTrue(updatedAccount.equals(newInput)); - - // Try search endpoint - searchAccount(input, null); - } - - @Test(groups = "slow", description = "Can retrieve the account balance") - public void testAccountWithBalance() throws Exception { - final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice(); - - final Account accountWithBalance = killBillClient.getAccount(accountJson.getAccountId(), true, false); - final BigDecimal accountBalance = accountWithBalance.getAccountBalance(); - Assert.assertTrue(accountBalance.compareTo(BigDecimal.ZERO) > 0); - } - - @Test(groups = "slow", description = "Cannot update a non-existent account") - public void testUpdateNonExistentAccount() throws Exception { - final Account input = getAccount(); - - Assert.assertNull(killBillClient.updateAccount(input, createdBy, reason, comment)); - } - - @Test(groups = "slow", description = "Cannot retrieve non-existent account") - public void testAccountNonExistent() throws Exception { - Assert.assertNull(killBillClient.getAccount(UUID.randomUUID())); - Assert.assertNull(killBillClient.getAccount(UUID.randomUUID().toString())); - } - - @Test(groups = "slow", description = "Can CRUD payment methods") - public void testAccountPaymentMethods() throws Exception { - final Account accountJson = createAccount(); - assertNotNull(accountJson); - - final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail(); - info.setProperties(getPaymentMethodCCProperties()); - PaymentMethod paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), true, PLUGIN_NAME, info); - final PaymentMethod paymentMethodCC = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment); - assertTrue(paymentMethodCC.getIsDefault()); - - // - // Add another payment method - // - final PaymentMethodPluginDetail info2 = new PaymentMethodPluginDetail(); - info2.setProperties(getPaymentMethodPaypalProperties()); - paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), false, PLUGIN_NAME, info2); - final PaymentMethod paymentMethodPP = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment); - assertFalse(paymentMethodPP.getIsDefault()); - - // - // FETCH ALL PAYMENT METHODS - // - List paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId()); - assertEquals(paymentMethods.size(), 2); - - // - // CHANGE DEFAULT - // - assertTrue(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault()); - assertFalse(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault()); - killBillClient.updateDefaultPaymentMethod(accountJson.getAccountId(), paymentMethodPP.getPaymentMethodId(), createdBy, reason, comment); - assertTrue(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault()); - assertFalse(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault()); - - // - // DELETE NON DEFAULT PM - // - killBillClient.deletePaymentMethod(paymentMethodCC.getPaymentMethodId(), false, createdBy, reason, comment); - - // - // FETCH ALL PAYMENT METHODS - // - paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId()); - assertEquals(paymentMethods.size(), 1); - - // - // DELETE DEFAULT PAYMENT METHOD (without special flag first) - // - try { - killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), false, createdBy, reason, comment); - fail(); - } catch (final KillBillClientException e) { - } - - // - // RETRY TO DELETE DEFAULT PAYMENT METHOD (with special flag this time) - // - killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), true, createdBy, reason, comment); - - // CHECK ACCOUNT IS NOW AUTO_PAY_OFF - final List tagsJson = killBillClient.getAccountTags(accountJson.getAccountId()); - Assert.assertEquals(tagsJson.size(), 1); - final Tag tagJson = tagsJson.get(0); - Assert.assertEquals(tagJson.getTagDefinitionName(), "AUTO_PAY_OFF"); - Assert.assertEquals(tagJson.getTagDefinitionId(), new UUID(0, 1)); - - // FETCH ACCOUNT AGAIN AND CHECK THERE IS NO DEFAULT PAYMENT METHOD SET - final Account updatedAccount = killBillClient.getAccount(accountJson.getAccountId()); - Assert.assertEquals(updatedAccount.getAccountId(), accountJson.getAccountId()); - Assert.assertNull(updatedAccount.getPaymentMethodId()); - - // - // FINALLY TRY TO REMOVE AUTO_PAY_OFF WITH NO DEFAULT PAYMENT METHOD ON ACCOUNT - // - try { - killBillClient.deleteAccountTag(accountJson.getAccountId(), new UUID(0, 1), createdBy, reason, comment); - } catch (final KillBillClientException e) { - } - } - - @Test(groups = "slow") - public void testAccountPaymentsWithRefund() throws Exception { - final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice(); - - // Verify payments - final InvoicePayments objFromJson = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId()); - Assert.assertEquals(objFromJson.size(), 1); - } - - @Test(groups = "slow", description = "Add tags to account") - public void testTags() throws Exception { - final Account input = createAccount(); - // Use tag definition for AUTO_PAY_OFF - final UUID autoPayOffId = new UUID(0, 1); - - // Add a tag - killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment); - - // Retrieves all tags - final List tags1 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL); - Assert.assertEquals(tags1.size(), 1); - Assert.assertEquals(tags1.get(0).getTagDefinitionId(), autoPayOffId); - - // Verify adding the same tag a second time doesn't do anything - killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment); - - // Retrieves all tags again - killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment); - final List tags2 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL); - Assert.assertEquals(tags2, tags1); - - // Verify audit logs - Assert.assertEquals(tags2.get(0).getAuditLogs().size(), 1); - final AuditLog auditLogJson = tags2.get(0).getAuditLogs().get(0); - Assert.assertEquals(auditLogJson.getChangeType(), "INSERT"); - Assert.assertEquals(auditLogJson.getChangedBy(), createdBy); - Assert.assertEquals(auditLogJson.getReasonCode(), reason); - Assert.assertEquals(auditLogJson.getComments(), comment); - Assert.assertNotNull(auditLogJson.getChangeDate()); - Assert.assertNotNull(auditLogJson.getUserToken()); - } - - @Test(groups = "slow", description = "Add custom fields to account") - public void testCustomFields() throws Exception { - final Account accountJson = createAccount(); - assertNotNull(accountJson); - - final Collection customFields = new LinkedList(); - customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "1", "value1", null)); - customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "2", "value2", null)); - customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "3", "value3", null)); - - killBillClient.createAccountCustomFields(accountJson.getAccountId(), customFields, createdBy, reason, comment); - - final List accountCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId()); - assertEquals(accountCustomFields.size(), 3); - - // Delete all custom fields for account - killBillClient.deleteAccountCustomFields(accountJson.getAccountId(), createdBy, reason, comment); - - final List remainingCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId()); - assertEquals(remainingCustomFields.size(), 0); - } - - @Test(groups = "slow", description = "Can paginate through all accounts") - public void testAccountsPagination() throws Exception { - for (int i = 0; i < 5; i++) { - createAccount(); - } - - final Accounts allAccounts = killBillClient.getAccounts(); - Assert.assertEquals(allAccounts.size(), 5); - - Accounts page = killBillClient.getAccounts(0L, 1L); - for (int i = 0; i < 5; i++) { - Assert.assertNotNull(page); - Assert.assertEquals(page.size(), 1); - Assert.assertEquals(page.get(0), allAccounts.get(i)); - page = page.getNext(); - } - Assert.assertNull(page); - } - - private void searchAccount(final Account input, @Nullable final Account output) throws Exception { - // Search by id - if (output != null) { - doSearchAccount(input.getAccountId().toString(), output); - } - - // Search by name - doSearchAccount(input.getName(), output); - - // Search by email - doSearchAccount(input.getEmail(), output); - - // Search by company name - doSearchAccount(input.getCompany(), output); - - // Search by external key. - // Note: we will always find a match since we don't update it - final List accountsByExternalKey = killBillClient.searchAccounts(input.getExternalKey()); - Assert.assertEquals(accountsByExternalKey.size(), 1); - Assert.assertEquals(accountsByExternalKey.get(0).getAccountId(), input.getAccountId()); - Assert.assertEquals(accountsByExternalKey.get(0).getExternalKey(), input.getExternalKey()); - } - - private void doSearchAccount(final String key, @Nullable final Account output) throws Exception { - final List accountsByKey = killBillClient.searchAccounts(key); - if (output == null) { - Assert.assertEquals(accountsByKey.size(), 0); - } else { - Assert.assertEquals(accountsByKey.size(), 1); - Assert.assertEquals(accountsByKey.get(0), output); - } - } -} From ca2e26498413e909a6d93a32cfb6da2ea8300e62 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 20 Aug 2015 17:25:03 -0700 Subject: [PATCH 033/137] See #355 --- .../billing/tenant/api/TenantCacheInvalidation.java | 13 +++++-------- .../billing/tenant/glue/DefaultTenantModule.java | 10 ---------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java index 776e6b1267..6b3a2bbb9f 100644 --- a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java +++ b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java @@ -42,6 +42,7 @@ import org.killbill.billing.util.config.TenantConfig; import org.killbill.bus.api.PersistentBus; import org.killbill.bus.api.PersistentBus.EventBusException; +import org.killbill.commons.concurrent.Executors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,22 +68,21 @@ public class TenantCacheInvalidation { private final Map cache; private final TenantBroadcastDao broadcastDao; - private final ScheduledExecutorService tenantExecutor; private final TenantConfig tenantConfig; private final PersistentBus eventBus; private final TenantDao tenantDao; private AtomicLong latestRecordIdProcessed; private volatile boolean isStopped; + private ScheduledExecutorService tenantExecutor; + @Inject public TenantCacheInvalidation(@Named(DefaultTenantModule.NO_CACHING_TENANT) final TenantBroadcastDao broadcastDao, - @Named(DefaultTenantModule.TENANT_EXECUTOR_NAMED) final ScheduledExecutorService tenantExecutor, @Named(DefaultTenantModule.NO_CACHING_TENANT) final TenantDao tenantDao, final PersistentBus eventBus, final TenantConfig tenantConfig) { this.cache = new HashMap(); this.broadcastDao = broadcastDao; - this.tenantExecutor = tenantExecutor; this.tenantConfig = tenantConfig; this.tenantDao = tenantDao; this.eventBus = eventBus; @@ -92,14 +92,11 @@ public TenantCacheInvalidation(@Named(DefaultTenantModule.NO_CACHING_TENANT) fin public void initialize() { final TenantBroadcastModelDao entry = broadcastDao.getLatestEntry(); this.latestRecordIdProcessed = entry != null ? new AtomicLong(entry.getRecordId()) : new AtomicLong(0L); - + this.tenantExecutor = Executors.newSingleThreadScheduledExecutor("TenantExecutor"); + this.isStopped = false; } public void start() { - if (isStopped) { - logger.warn("TenantExecutor is in a stopped state, abort start sequence"); - return; - } final TimeUnit pendingRateUnit = tenantConfig.getTenantBroadcastServiceRunningRate().getUnit(); final long pendingPeriod = tenantConfig.getTenantBroadcastServiceRunningRate().getPeriod(); tenantExecutor.scheduleAtFixedRate(new TenantCacheInvalidationRunnable(this, broadcastDao, tenantDao), pendingPeriod, pendingPeriod, pendingRateUnit); diff --git a/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java b/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java index 6770c67440..af1c9f7afe 100644 --- a/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java +++ b/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java @@ -18,8 +18,6 @@ package org.killbill.billing.tenant.glue; -import java.util.concurrent.ScheduledExecutorService; - import org.killbill.billing.glue.TenantModule; import org.killbill.billing.platform.api.KillbillConfigSource; import org.killbill.billing.tenant.api.DefaultTenantInternalApi; @@ -46,8 +44,6 @@ public class DefaultTenantModule extends KillBillModule implements TenantModule public static final String NO_CACHING_TENANT = "NoCachingTenant"; - public static final String TENANT_EXECUTOR_NAMED = "TenantExecutor"; - public DefaultTenantModule(final KillbillConfigSource configSource) { super(configSource); } @@ -79,11 +75,6 @@ public void installTenantCacheInvalidation() { bind(TenantCacheInvalidation.class).asEagerSingleton(); } - protected void installExecutor() { - final ScheduledExecutorService tenantExecutor = org.killbill.commons.concurrent.Executors.newSingleThreadScheduledExecutor("TenantExecutor"); - bind(ScheduledExecutorService.class).annotatedWith(Names.named(TENANT_EXECUTOR_NAMED)).toInstance(tenantExecutor); - } - @Override protected void configure() { installConfig(); @@ -91,6 +82,5 @@ protected void configure() { installTenantService(); installTenantUserApi(); installTenantCacheInvalidation(); - installExecutor(); } } From 9bf8ab911f2384467c461f858a0a31ceeb702506 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 21 Aug 2015 10:34:39 -0700 Subject: [PATCH 034/137] payment: Fix broken payment tests after introducing the PaymentExecutors --- .../janitor/IncompletePaymentAttemptTask.java | 13 ++- .../billing/payment/core/janitor/Janitor.java | 95 +++++++++++++++++-- .../billing/payment/glue/PaymentModule.java | 2 - .../billing/payment/PaymentTestSuiteNoDB.java | 2 + .../PaymentTestSuiteWithEmbeddedDB.java | 2 + .../killbill/billing/payment/TestJanitor.java | 8 +- .../dispatcher/TestPluginDispatcher.java | 15 ++- 7 files changed, 116 insertions(+), 21 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java index e7f2318b2c..9851afb17f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java @@ -70,11 +70,16 @@ public class IncompletePaymentAttemptTask extends CompletionTaskBase pluginRegistry, final GlobalLocker locker) { + final OSGIServiceRegistration pluginRegistry, + final GlobalLocker locker) { super(internalCallContextFactory, paymentConfig, paymentDao, clock, paymentStateMachineHelper, retrySMHelper, accountInternalApi, pluginRegistry, locker); this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner; } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java index 2c21d9c4dd..fbac8e0033 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java @@ -24,10 +24,20 @@ import javax.inject.Inject; import org.joda.time.DateTime; +import org.killbill.billing.account.api.AccountInternalApi; import org.killbill.billing.events.PaymentInternalEvent; +import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.core.PaymentExecutors; +import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper; +import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper; +import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner; +import org.killbill.billing.payment.dao.PaymentDao; import org.killbill.billing.payment.glue.DefaultPaymentService; +import org.killbill.billing.payment.plugin.api.PaymentPluginApi; +import org.killbill.billing.util.callcontext.InternalCallContextFactory; import org.killbill.billing.util.config.PaymentConfig; +import org.killbill.clock.Clock; +import org.killbill.commons.locker.GlobalLocker; import org.killbill.notificationq.api.NotificationEvent; import org.killbill.notificationq.api.NotificationQueue; import org.killbill.notificationq.api.NotificationQueueService; @@ -49,29 +59,75 @@ public class Janitor { private final NotificationQueueService notificationQueueService; private final PaymentConfig paymentConfig; - private final IncompletePaymentAttemptTask incompletePaymentAttemptTask; - private final IncompletePaymentTransactionTask incompletePaymentTransactionTask; private final PaymentExecutors paymentExecutors; + private final Clock clock; + private final PaymentDao paymentDao; + private final InternalCallContextFactory internalCallContextFactory; + private final PaymentStateMachineHelper paymentStateMachineHelper; + private final PaymentControlStateMachineHelper retrySMHelper; + private final AccountInternalApi accountInternalApi; + private final OSGIServiceRegistration pluginRegistry; + private final GlobalLocker locker; + private final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner; + + + + private IncompletePaymentAttemptTask incompletePaymentAttemptTask; + private IncompletePaymentTransactionTask incompletePaymentTransactionTask; private NotificationQueue janitorQueue; private ScheduledExecutorService janitorExecutor; private volatile boolean isStopped; @Inject - public Janitor(final PaymentConfig paymentConfig, + public Janitor(final InternalCallContextFactory internalCallContextFactory, + final PaymentDao paymentDao, + final Clock clock, + final PaymentStateMachineHelper paymentStateMachineHelper, + final PaymentControlStateMachineHelper retrySMHelper, + final AccountInternalApi accountInternalApi, + final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner, + final OSGIServiceRegistration pluginRegistry, + final GlobalLocker locker, + final PaymentConfig paymentConfig, final NotificationQueueService notificationQueueService, - final PaymentExecutors paymentExecutors, - final IncompletePaymentAttemptTask incompletePaymentAttemptTask, - final IncompletePaymentTransactionTask incompletePaymentTransactionTask) { + final PaymentExecutors paymentExecutors) { this.notificationQueueService = notificationQueueService; this.paymentExecutors = paymentExecutors; this.paymentConfig = paymentConfig; - this.incompletePaymentAttemptTask = incompletePaymentAttemptTask; - this.incompletePaymentTransactionTask = incompletePaymentTransactionTask; + this.internalCallContextFactory = internalCallContextFactory; + this.paymentDao = paymentDao; + this.clock = clock; + this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner; + this.paymentStateMachineHelper = paymentStateMachineHelper; + this.retrySMHelper = retrySMHelper; + this.accountInternalApi = accountInternalApi; + this.pluginRegistry = pluginRegistry; + this.locker = locker; } + /* + public IncompletePaymentAttemptTask(final InternalCallContextFactory internalCallContextFactory, + final PaymentConfig paymentConfig, + final PaymentDao paymentDao, + final Clock clock, + final PaymentStateMachineHelper paymentStateMachineHelper, + final PaymentControlStateMachineHelper retrySMHelper, + final AccountInternalApi accountInternalApi, + final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner, + final OSGIServiceRegistration pluginRegistry, + final GlobalLocker locker) { + + + public IncompletePaymentTransactionTask(final InternalCallContextFactory internalCallContextFactory, final PaymentConfig paymentConfig, + final PaymentDao paymentDao, final Clock clock, + final PaymentStateMachineHelper paymentStateMachineHelper, final PaymentControlStateMachineHelper retrySMHelper, final AccountInternalApi accountInternalApi, + final OSGIServiceRegistration pluginRegistry, final GlobalLocker locker) { + + */ + public void initialize() throws NotificationQueueAlreadyExists { janitorQueue = notificationQueueService.createNotificationQueue(DefaultPaymentService.SERVICE_NAME, QUEUE_NAME, @@ -90,6 +146,29 @@ public void handleReadyNotification(final NotificationEvent notificationKey, fin } } ); + + this.incompletePaymentAttemptTask = new IncompletePaymentAttemptTask(internalCallContextFactory, + paymentConfig, + paymentDao, + clock, + paymentStateMachineHelper, + retrySMHelper, + accountInternalApi, + pluginControlledPaymentAutomatonRunner, + pluginRegistry, + locker); + + this.incompletePaymentTransactionTask = new IncompletePaymentTransactionTask(internalCallContextFactory, + paymentConfig, + paymentDao, + clock, + paymentStateMachineHelper, + retrySMHelper, + accountInternalApi, + pluginRegistry, + locker); + + incompletePaymentTransactionTask.attachJanitorQueue(janitorQueue); incompletePaymentAttemptTask.attachJanitorQueue(janitorQueue); } diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java index b249c8b49f..c9a0c74eb6 100644 --- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java +++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java @@ -92,8 +92,6 @@ protected void installPaymentProviderPlugins(final PaymentConfig config) { } protected void installJanitor() { - bind(IncompletePaymentTransactionTask.class).asEagerSingleton(); - bind(IncompletePaymentAttemptTask.class).asEagerSingleton(); bind(Janitor.class).asEagerSingleton(); } diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java index 8c45a5a8d1..9af2ee5695 100644 --- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java +++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java @@ -103,11 +103,13 @@ protected void beforeClass() throws Exception { @BeforeMethod(groups = "fast") public void beforeMethod() throws Exception { eventBus.start(); + paymentExecutors.initialize(); Profiling.resetPerThreadProfilingData(); } @AfterMethod(groups = "fast") public void afterMethod() throws Exception { + paymentExecutors.stop(); eventBus.stop(); } } diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java index aefb209af8..14072c315c 100644 --- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java +++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java @@ -90,6 +90,7 @@ protected void beforeClass() throws Exception { @BeforeMethod(groups = "slow") public void beforeMethod() throws Exception { super.beforeMethod(); + paymentExecutors.initialize(); eventBus.start(); Profiling.resetPerThreadProfilingData(); clock.resetDeltaFromReality(); @@ -99,5 +100,6 @@ public void beforeMethod() throws Exception { @AfterMethod(groups = "slow") public void afterMethod() throws Exception { eventBus.stop(); + paymentExecutors.stop(); } } diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java index 8303dc6ad1..1753af81ed 100644 --- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java +++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java @@ -114,20 +114,17 @@ protected KillbillConfigSource getConfigSource() { protected void beforeClass() throws Exception { super.beforeClass(); mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME); - paymentExecutors.initialize(); - janitor.initialize(); - janitor.start(); } @AfterClass(groups = "slow") protected void afterClass() throws Exception { - janitor.stop(); - paymentExecutors.stop(); } @BeforeMethod(groups = "slow") public void beforeMethod() throws Exception { super.beforeMethod(); + janitor.initialize(); + janitor.start(); eventBus.register(handler); testListener.reset(); eventBus.register(testListener); @@ -137,6 +134,7 @@ public void beforeMethod() throws Exception { @AfterMethod(groups = "slow") public void afterMethod() throws Exception { + janitor.stop(); eventBus.unregister(handler); eventBus.unregister(testListener); super.afterMethod(); diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java index 86365e5863..835df39e38 100644 --- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java +++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java @@ -26,16 +26,27 @@ import org.killbill.billing.payment.PaymentTestSuiteNoDB; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; +import org.killbill.commons.profiling.Profiling; import org.killbill.commons.request.Request; import org.killbill.commons.request.RequestData; import org.testng.Assert; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class TestPluginDispatcher extends PaymentTestSuiteNoDB { - private final PluginDispatcher voidPluginDispatcher = new PluginDispatcher(10, paymentExecutors); + private PluginDispatcher voidPluginDispatcher; + + private PluginDispatcher stringPluginDispatcher; + + @BeforeMethod(groups = "fast") + public void beforeMethod() throws Exception { + super.beforeMethod(); + eventBus.start(); + voidPluginDispatcher = new PluginDispatcher(10, paymentExecutors); + stringPluginDispatcher = new PluginDispatcher(1, paymentExecutors); + } - private final PluginDispatcher stringPluginDispatcher = new PluginDispatcher(1, paymentExecutors); @Test(groups = "fast") public void testDispatchWithTimeout() throws TimeoutException, PaymentApiException { From 681cea47f9e1104f80ccc5d4495ed477d73f1f0d Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Fri, 21 Aug 2015 17:50:01 +0000 Subject: [PATCH 035/137] Update oss version with released versions --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8deb6da3a6..57fd16de7e 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.38-SNAPSHOT + 0.38 killbill 0.15.3-SNAPSHOT From 6be7d1ead07057e081d3fac9ec21e4fb4eb2b0d2 Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Fri, 21 Aug 2015 17:51:14 +0000 Subject: [PATCH 036/137] pom.xml: updates for release 0.15.3 Release for milestone 0.15.3: https://github.com/killbill/killbill/issues?q=is%3Aissue+milestone%3ARelease-0.15.3+is%3Aclosed Signed-off-by: Kill Bill core team --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index c5b5ee6b99..4118402711 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +0.15.3 + Release for milestone 0.15.3: https://github.com/killbill/killbill/issues?q=is%3Aissue+milestone%3ARelease-0.15.3+is%3Aclosed + 0.15.2 See https://github.com/killbill/killbill/issues?q=milestone%3ARelease-0.15.2+is%3Aclosed From 2467f534e4a4a5ca2944d3600ee171836f9e496e Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Fri, 21 Aug 2015 17:57:45 +0000 Subject: [PATCH 037/137] [maven-release-plugin] prepare release killbill-0.15.3 --- account/pom.xml | 2 +- api/pom.xml | 2 +- beatrix/pom.xml | 2 +- catalog/pom.xml | 2 +- currency/pom.xml | 2 +- entitlement/pom.xml | 2 +- invoice/pom.xml | 2 +- jaxrs/pom.xml | 2 +- junction/pom.xml | 2 +- overdue/pom.xml | 2 +- payment/pom.xml | 2 +- pom.xml | 2 +- profiles/killbill/pom.xml | 2 +- profiles/killpay/pom.xml | 2 +- profiles/pom.xml | 2 +- subscription/pom.xml | 2 +- tenant/pom.xml | 2 +- usage/pom.xml | 2 +- util/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/account/pom.xml b/account/pom.xml index e331e15af2..d205e00fa1 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-account diff --git a/api/pom.xml b/api/pom.xml index f26b6fc1bf..0b9b9cb21f 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-internal-api diff --git a/beatrix/pom.xml b/beatrix/pom.xml index 8613028dba..07b1a56062 100644 --- a/beatrix/pom.xml +++ b/beatrix/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-beatrix diff --git a/catalog/pom.xml b/catalog/pom.xml index abdc1b92c5..547812731f 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-catalog diff --git a/currency/pom.xml b/currency/pom.xml index 62ea8e3846..7bdf9969b0 100644 --- a/currency/pom.xml +++ b/currency/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-currency diff --git a/entitlement/pom.xml b/entitlement/pom.xml index ea853a24b5..5dc67f862e 100644 --- a/entitlement/pom.xml +++ b/entitlement/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-entitlement diff --git a/invoice/pom.xml b/invoice/pom.xml index 6b48d16abe..fff38b2420 100644 --- a/invoice/pom.xml +++ b/invoice/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-invoice diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index 5a8de806ac..35af5ce43a 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-jaxrs diff --git a/junction/pom.xml b/junction/pom.xml index 67b286a9f7..8495daeed1 100644 --- a/junction/pom.xml +++ b/junction/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-junction diff --git a/overdue/pom.xml b/overdue/pom.xml index 8c77086297..0846555152 100644 --- a/overdue/pom.xml +++ b/overdue/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-overdue diff --git a/payment/pom.xml b/payment/pom.xml index 83e453a254..ad8ebf4906 100644 --- a/payment/pom.xml +++ b/payment/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-payment diff --git a/pom.xml b/pom.xml index 57fd16de7e..ed9823fdcc 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 0.38 killbill - 0.15.3-SNAPSHOT + 0.15.3 pom killbill Library for managing recurring subscriptions and the associated billing diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml index 47ce217769..4b8e91f59c 100644 --- a/profiles/killbill/pom.xml +++ b/profiles/killbill/pom.xml @@ -21,7 +21,7 @@ killbill-profiles org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-profiles-killbill diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml index 4544cc3ae2..a7f1af66e0 100644 --- a/profiles/killpay/pom.xml +++ b/profiles/killpay/pom.xml @@ -20,7 +20,7 @@ killbill-profiles org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-profiles-killpay diff --git a/profiles/pom.xml b/profiles/pom.xml index 6f18124b66..6b1e23ae57 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-profiles diff --git a/subscription/pom.xml b/subscription/pom.xml index efcb4d48b0..127c3d6e6d 100644 --- a/subscription/pom.xml +++ b/subscription/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-subscription diff --git a/tenant/pom.xml b/tenant/pom.xml index aef172bdea..a4771069b5 100644 --- a/tenant/pom.xml +++ b/tenant/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-tenant diff --git a/usage/pom.xml b/usage/pom.xml index 4cd57d89d3..a1b3054122 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-usage diff --git a/util/pom.xml b/util/pom.xml index 0a88a19191..08f35398af 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.3-SNAPSHOT + 0.15.3 ../pom.xml killbill-util From 2aa4e71e1dc34f499fac2b6845752acd1f97bdeb Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Fri, 21 Aug 2015 17:57:48 +0000 Subject: [PATCH 038/137] [maven-release-plugin] prepare for next development iteration --- account/pom.xml | 2 +- api/pom.xml | 2 +- beatrix/pom.xml | 2 +- catalog/pom.xml | 2 +- currency/pom.xml | 2 +- entitlement/pom.xml | 2 +- invoice/pom.xml | 2 +- jaxrs/pom.xml | 2 +- junction/pom.xml | 2 +- overdue/pom.xml | 2 +- payment/pom.xml | 2 +- pom.xml | 2 +- profiles/killbill/pom.xml | 2 +- profiles/killpay/pom.xml | 2 +- profiles/pom.xml | 2 +- subscription/pom.xml | 2 +- tenant/pom.xml | 2 +- usage/pom.xml | 2 +- util/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/account/pom.xml b/account/pom.xml index d205e00fa1..44b911abcc 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-account diff --git a/api/pom.xml b/api/pom.xml index 0b9b9cb21f..5b8aad5904 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-internal-api diff --git a/beatrix/pom.xml b/beatrix/pom.xml index 07b1a56062..c6bdbdac1d 100644 --- a/beatrix/pom.xml +++ b/beatrix/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-beatrix diff --git a/catalog/pom.xml b/catalog/pom.xml index 547812731f..e6ad7c8f0d 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-catalog diff --git a/currency/pom.xml b/currency/pom.xml index 7bdf9969b0..55e978a8d6 100644 --- a/currency/pom.xml +++ b/currency/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-currency diff --git a/entitlement/pom.xml b/entitlement/pom.xml index 5dc67f862e..7895360c3b 100644 --- a/entitlement/pom.xml +++ b/entitlement/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-entitlement diff --git a/invoice/pom.xml b/invoice/pom.xml index fff38b2420..b1460fe706 100644 --- a/invoice/pom.xml +++ b/invoice/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-invoice diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index 35af5ce43a..fbb674e131 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-jaxrs diff --git a/junction/pom.xml b/junction/pom.xml index 8495daeed1..2f403da626 100644 --- a/junction/pom.xml +++ b/junction/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-junction diff --git a/overdue/pom.xml b/overdue/pom.xml index 0846555152..03b322c471 100644 --- a/overdue/pom.xml +++ b/overdue/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-overdue diff --git a/payment/pom.xml b/payment/pom.xml index ad8ebf4906..ef45525095 100644 --- a/payment/pom.xml +++ b/payment/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-payment diff --git a/pom.xml b/pom.xml index ed9823fdcc..2ca99c57dc 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 0.38 killbill - 0.15.3 + 0.15.4-SNAPSHOT pom killbill Library for managing recurring subscriptions and the associated billing diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml index 4b8e91f59c..78f963d987 100644 --- a/profiles/killbill/pom.xml +++ b/profiles/killbill/pom.xml @@ -21,7 +21,7 @@ killbill-profiles org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-profiles-killbill diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml index a7f1af66e0..231f34320b 100644 --- a/profiles/killpay/pom.xml +++ b/profiles/killpay/pom.xml @@ -20,7 +20,7 @@ killbill-profiles org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-profiles-killpay diff --git a/profiles/pom.xml b/profiles/pom.xml index 6b1e23ae57..88e8cd00ee 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-profiles diff --git a/subscription/pom.xml b/subscription/pom.xml index 127c3d6e6d..cd54166128 100644 --- a/subscription/pom.xml +++ b/subscription/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-subscription diff --git a/tenant/pom.xml b/tenant/pom.xml index a4771069b5..a8d2a80a6b 100644 --- a/tenant/pom.xml +++ b/tenant/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-tenant diff --git a/usage/pom.xml b/usage/pom.xml index a1b3054122..16205bde4f 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-usage diff --git a/util/pom.xml b/util/pom.xml index 08f35398af..bfc65cee27 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.3 + 0.15.4-SNAPSHOT ../pom.xml killbill-util From e9655b2f35b3152235770cc350269002261e8222 Mon Sep 17 00:00:00 2001 From: David Xie Date: Mon, 24 Aug 2015 15:39:03 +0800 Subject: [PATCH 039/137] Add -H support for db-helper --- bin/db-helper | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/bin/db-helper b/bin/db-helper index 929c59a8eb..95761e605c 100755 --- a/bin/db-helper +++ b/bin/db-helper @@ -19,7 +19,7 @@ # # ################################################################################### -#set -x +# set -x HERE=`cd \`dirname $0\`; pwd` TOP=$HERE/.. @@ -27,6 +27,7 @@ TOP=$HERE/.. POM="$TOP/pom.xml" ACTION= +HOST="localhost" DATABASE="killbill" USER="root" PWD="root" @@ -42,6 +43,7 @@ SKIP="(server)" function usage() { echo -n "./db_helper " echo -n " -a " + echo -n " -H MySQL host (default = localhost)" echo -n " -d database_name (default = killbill)" echo -n " -u user_name (default = root)" echo -n " -p password (default = root)" @@ -106,7 +108,7 @@ function create_ddl_file() { local tmp="/tmp/ddl-$DATABASE.$$" touch $tmp - echo "/*! use $DATABASE; */" >> $tmp + echo "use $DATABASE;" >> $tmp echo "" >> $tmp for d in $ddls; do cat $d >> $tmp @@ -120,10 +122,11 @@ function cleanup() { } -while getopts ":a:d:u:p:f:t" options; do +while getopts ":a:d:H:u:p:f:t" options; do case $options in a ) ACTION=$OPTARG;; d ) DATABASE=$OPTARG;; + H ) HOST=$OPTARG;; u ) USER=$OPTARG;; p ) PWD=$OPTARG;; t ) TEST_ALSO=1;; @@ -152,15 +155,15 @@ fi if [ $ACTION == "create" ]; then DDL_FILE=`create_ddl_file` - echo "Applying new schema $tmp to database $DATABASE" - mysql -u $USER --password=$PWD < $DDL_FILE + echo "Applying new schema to database $DATABASE" + mysql -h $HOST -u $USER --password=$PWD < $DDL_FILE fi if [ $ACTION == "clean" ]; then DDL_FILE=`create_ddl_file` CLEAN_FILE=`create_clean_file $DDL_FILE` echo "Cleaning db tables on database $DATABASE" - mysql -u $USER --password=$PWD < $DDL_FILE + mysql -h $HOST -u $USER --password=$PWD < $DDL_FILE fi cleanup From 3de4599e4b8994a058279b79d2db5bee9e9fa21e Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 26 Aug 2015 16:02:10 -0700 Subject: [PATCH 040/137] Remove subscription base deactivate/reactivate API which is not used anymore --- .../billing/subscription/events/EventBase.java | 10 ---------- .../subscription/events/SubscriptionBaseEvent.java | 4 ---- .../engine/dao/MockSubscriptionDaoMemory.java | 6 +++--- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/EventBase.java b/subscription/src/main/java/org/killbill/billing/subscription/events/EventBase.java index d80cfc1600..926ccafb00 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/EventBase.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/EventBase.java @@ -104,16 +104,6 @@ public boolean isActive() { return isActive; } - @Override - public void deactivate() { - this.isActive = false; - } - - @Override - public void reactivate() { - this.isActive = true; - } - @Override public void setTotalOrdering(final long totalOrdering) { this.totalOrdering = totalOrdering; diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java index ceb0211cd6..225cf92a44 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java @@ -42,10 +42,6 @@ public enum EventType { public boolean isActive(); - public void deactivate(); - - public void reactivate(); - public DateTime getProcessedDate(); public DateTime getRequestedDate(); diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java index 9b608c0c46..6cc963b926 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java @@ -375,7 +375,7 @@ private void cancelNextPhaseEvent(final UUID subscriptionId, final InternalTenan } if (cur.getType() == EventType.PHASE && cur.getEffectiveDate().isAfter(clock.getUTCNow())) { - cur.deactivate(); + it.remove(); break; } @@ -396,7 +396,7 @@ private void cancelNextChangeEvent(final UUID subscriptionId) { if (cur.getType() == EventType.API_USER && ApiEventType.CHANGE == ((ApiEvent) cur).getEventType() && cur.getEffectiveDate().isAfter(clock.getUTCNow())) { - cur.deactivate(); + it.remove(); break; } } @@ -417,7 +417,7 @@ public void uncancelSubscription(final DefaultSubscriptionBase subscription, fin } if (cur.getType() == EventType.API_USER && ((ApiEvent) cur).getEventType() == ApiEventType.CANCEL) { - cur.deactivate(); + it.remove(); foundCancel = true; break; } From c972061cf9d9f8683e93ed686e39a0c85543e0ad Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 26 Aug 2015 16:11:45 -0700 Subject: [PATCH 041/137] Remove unused processedDate for subscription events (similar to createdDate and was never used) --- .../DefaultSubscriptionBaseMigrationApi.java | 1 - .../DefaultSubscriptionBaseTransferApi.java | 2 -- .../user/DefaultSubscriptionBaseApiService.java | 5 ----- .../engine/dao/DefaultSubscriptionDao.java | 4 +--- .../dao/model/SubscriptionEventModelDao.java | 1 - .../billing/subscription/events/EventBase.java | 11 ----------- .../subscription/events/EventBaseBuilder.java | 11 ----------- .../events/SubscriptionBaseEvent.java | 2 -- .../events/phase/PhaseEventData.java | 2 -- .../subscription/events/user/ApiEventBase.java | 1 - .../events/user/ApiEventMigrateBilling.java | 17 ----------------- 11 files changed, 1 insertion(+), 56 deletions(-) diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/migration/DefaultSubscriptionBaseMigrationApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/migration/DefaultSubscriptionBaseMigrationApi.java index 39fdf2bd1e..2fc95850ba 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/migration/DefaultSubscriptionBaseMigrationApi.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/migration/DefaultSubscriptionBaseMigrationApi.java @@ -206,7 +206,6 @@ private List toEvents(final DefaultSubscriptionBase defau .setEventPriceList(cur.getPriceList()) .setActiveVersion(defaultSubscriptionBase.getActiveVersion()) .setEffectiveDate(cur.getEventTime()) - .setProcessedDate(now) .setRequestedDate(now) .setFromDisk(true); diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java index 8a36c06ed5..4abe2f72e7 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java @@ -102,7 +102,6 @@ private SubscriptionBaseEvent createEvent(final boolean firstEvent, final Existi .setEventPlanPhase(currentPhase.getName()) .setEventPriceList(spec.getPriceListName()) .setActiveVersion(subscription.getActiveVersion()) - .setProcessedDate(clock.getUTCNow()) .setEffectiveDate(effectiveDate) .setRequestedDate(effectiveDate) .setFromDisk(true); @@ -241,7 +240,6 @@ public SubscriptionBaseBundle transferBundle(final UUID sourceAccountId, final U final SubscriptionBaseEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder() .setSubscriptionId(cur.getId()) .setActiveVersion(cur.getActiveVersion()) - .setProcessedDate(clock.getUTCNow()) .setEffectiveDate(effectiveCancelDate) .setRequestedDate(effectiveTransferDate) .setFromDisk(true)); diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java index 9601adfc5e..5ff0345d7e 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java @@ -238,7 +238,6 @@ public boolean uncancel(final DefaultSubscriptionBase subscription, final CallCo final SubscriptionBaseEvent uncancelEvent = new ApiEventUncancel(new ApiEventBuilder() .setSubscriptionId(subscription.getId()) .setActiveVersion(subscription.getActiveVersion()) - .setProcessedDate(now) .setRequestedDate(now) .setEffectiveDate(now) .setFromDisk(true)); @@ -380,7 +379,6 @@ public List getEventsOnCreation(final UUID bundleId, fina .setEventPlanPhase(curAndNextPhases[0].getPhase().getName()) .setEventPriceList(realPriceList) .setActiveVersion(activeVersion) - .setProcessedDate(processedDate) .setEffectiveDate(effectiveDate) .setRequestedDate(requestedDate) .setFromDisk(true); @@ -410,7 +408,6 @@ public List getEventsOnChangePlan(final DefaultSubscripti .setEventPlanPhase(currentTimedPhase.getPhase().getName()) .setEventPriceList(newPriceList) .setActiveVersion(subscription.getActiveVersion()) - .setProcessedDate(processedDate) .setEffectiveDate(effectiveDate) .setRequestedDate(requestedDate) .setFromDisk(true)); @@ -443,7 +440,6 @@ public List getEventsOnCancelPlan(final DefaultSubscripti final SubscriptionBaseEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder() .setSubscriptionId(subscription.getId()) .setActiveVersion(subscription.getActiveVersion()) - .setProcessedDate(processedDate) .setEffectiveDate(effectiveDate) .setRequestedDate(requestedDate) .setFromDisk(true)); @@ -495,7 +491,6 @@ private List addCancellationAddOnForEventsIfRequired(fi final SubscriptionBaseEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder() .setSubscriptionId(cur.getId()) .setActiveVersion(cur.getActiveVersion()) - .setProcessedDate(processedDate) .setEffectiveDate(effectiveDate) .setRequestedDate(requestedDate) .setFromDisk(true)); diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java index 7c9158972f..9bbbd32864 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java @@ -699,7 +699,6 @@ final List reinsertFutureMigrateBillingEventOnChangeFromT .setUpdatedDate(now) .setRequestedDate(migrateBillingEvent.getRequestedDate()) .setEffectiveDate(migrateBillingEvent.getEffectiveDate()) - .setProcessedDate(now) .setActiveVersion(migrateBillingEvent.getCurrentVersion()) .setEventPlan(prevPlan) .setEventPlanPhase(prevPhase) @@ -905,7 +904,6 @@ public int compare(final SubscriptionBase o1, final SubscriptionBase o2) { final SubscriptionBaseEvent addOnCancelEvent = new ApiEventCancel(new ApiEventBuilder() .setSubscriptionId(reloaded.getId()) .setActiveVersion(((DefaultSubscriptionBase) reloaded).getActiveVersion()) - .setProcessedDate(now) .setEffectiveDate(baseTriggerEventForAddOnCancellation.getEffectiveDate()) .setRequestedDate(now) .setCreatedDate(baseTriggerEventForAddOnCancellation.getCreatedDate()) @@ -943,7 +941,7 @@ private void mergeDryRunEvents(final UUID subscriptionId, final List builder) { this.updatedDate = builder.getUpdatedDate(); this.requestedDate = builder.getRequestedDate(); this.effectiveDate = builder.getEffectiveDate(); - this.processedDate = builder.getProcessedDate(); this.activeVersion = builder.getActiveVersion(); this.isActive = builder.isActive(); } @@ -59,11 +57,6 @@ public DateTime getEffectiveDate() { return effectiveDate; } - @Override - public DateTime getProcessedDate() { - return processedDate; - } - @Override public UUID getSubscriptionId() { return subscriptionId; @@ -128,10 +121,6 @@ public int compareTo(final SubscriptionBaseEvent other) { return -1; } else if (effectiveDate.isAfter(other.getEffectiveDate())) { return 1; - } else if (processedDate.isBefore(other.getProcessedDate())) { - return -1; - } else if (processedDate.isAfter(other.getProcessedDate())) { - return 1; } else if (requestedDate.isBefore(other.getRequestedDate())) { return -1; } else if (requestedDate.isAfter(other.getRequestedDate())) { diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/EventBaseBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/events/EventBaseBuilder.java index 1c90547a53..ea022cf7e0 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/EventBaseBuilder.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/EventBaseBuilder.java @@ -31,7 +31,6 @@ public class EventBaseBuilder> { private DateTime updatedDate; private DateTime requestedDate; private DateTime effectiveDate; - private DateTime processedDate; private long activeVersion; private boolean isActive; @@ -46,7 +45,6 @@ public EventBaseBuilder(final EventBaseBuilder copy) { this.subscriptionId = copy.subscriptionId; this.requestedDate = copy.requestedDate; this.effectiveDate = copy.effectiveDate; - this.processedDate = copy.processedDate; this.createdDate = copy.getCreatedDate(); this.activeVersion = copy.activeVersion; this.isActive = copy.isActive; @@ -88,11 +86,6 @@ public T setEffectiveDate(final DateTime effectiveDate) { return (T) this; } - public T setProcessedDate(final DateTime processedDate) { - this.processedDate = processedDate; - return (T) this; - } - public T setActiveVersion(final long activeVersion) { this.activeVersion = activeVersion; return (T) this; @@ -131,10 +124,6 @@ public DateTime getEffectiveDate() { return effectiveDate; } - public DateTime getProcessedDate() { - return processedDate; - } - public long getActiveVersion() { return activeVersion; } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java index 225cf92a44..23b5b20180 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java @@ -42,8 +42,6 @@ public enum EventType { public boolean isActive(); - public DateTime getProcessedDate(); - public DateTime getRequestedDate(); public DateTime getEffectiveDate(); diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/phase/PhaseEventData.java b/subscription/src/main/java/org/killbill/billing/subscription/events/phase/PhaseEventData.java index b3da3d8aaa..6da067cc43 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/phase/PhaseEventData.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/phase/PhaseEventData.java @@ -53,7 +53,6 @@ public String toString() { + ", getRequestedDate()=" + getRequestedDate() + ", getEffectiveDate()=" + getEffectiveDate() + ", getActiveVersion()=" + getActiveVersion() - + ", getProcessedDate()=" + getProcessedDate() + ", getSubscriptionId()=" + getSubscriptionId() + ", isActive()=" + isActive() + "]\n"; } @@ -65,7 +64,6 @@ public static PhaseEvent createNextPhaseEvent(final UUID subscriptionId, final l .setSubscriptionId(subscriptionId) .setRequestedDate(now) .setEffectiveDate(effectiveDate) - .setProcessedDate(now) .setActiveVersion(activeVersion) .setPhaseName(phaseName)); } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBase.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBase.java index ce462fe125..140b2a40d9 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBase.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBase.java @@ -80,7 +80,6 @@ public String toString() { + ", getRequestedDate()=" + getRequestedDate() + ", getEffectiveDate()=" + getEffectiveDate() + ", getActiveVersion()=" + getActiveVersion() - + ", getProcessedDate()=" + getProcessedDate() + ", getSubscriptionId()=" + getSubscriptionId() + ", isActive()=" + isActive() + "]"; } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateBilling.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateBilling.java index 8cdd287f28..d4b17c16be 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateBilling.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateBilling.java @@ -16,25 +16,8 @@ package org.killbill.billing.subscription.events.user; -import org.joda.time.DateTime; - public class ApiEventMigrateBilling extends ApiEventBase { public ApiEventMigrateBilling(final ApiEventBuilder builder) { super(builder.setEventType(ApiEventType.MIGRATE_BILLING)); } - - public ApiEventMigrateBilling(final ApiEventMigrateSubscription input, final DateTime ctd) { - super(new ApiEventBuilder() - .setSubscriptionId(input.getSubscriptionId()) - .setEventPlan(input.getEventPlan()) - .setEventPlanPhase(input.getEventPlanPhase()) - .setEventPriceList(input.getPriceList()) - .setActiveVersion(input.getActiveVersion()) - .setEffectiveDate(ctd) - .setProcessedDate(input.getProcessedDate()) - .setRequestedDate(input.getRequestedDate()) - .setFromDisk(true) - .setEventType(ApiEventType.MIGRATE_BILLING)); - } - } From 5aca0cdd7fa6ad0a5f7987d9e2d904ce968bf3e7 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 26 Aug 2015 17:18:22 -0700 Subject: [PATCH 042/137] subscription: Fixes #377 Refactor the EventBuilder to make better use of them and remove all setters in SubscriptionBaseEvent. --- .../DefaultSubscriptionBaseMigrationApi.java | 4 +- .../svcs/DefaultSubscriptionInternalApi.java | 7 +-- .../DefaultSubscriptionBaseTimeline.java | 2 +- .../api/user/DefaultSubscriptionBase.java | 6 +-- .../engine/dao/DefaultSubscriptionDao.java | 18 +++++-- .../dao/model/SubscriptionEventModelDao.java | 31 +++--------- .../subscription/events/EventBase.java | 18 ++----- .../subscription/events/EventBaseBuilder.java | 18 ++++++- .../events/SubscriptionBaseEvent.java | 4 -- .../events/phase/PhaseEventBuilder.java | 9 ++++ .../subscription/events/user/ApiEvent.java | 2 +- .../events/user/ApiEventBase.java | 12 ++--- .../events/user/ApiEventBuilder.java | 47 ++++++++++++++++--- .../events/user/ApiEventCancel.java | 2 +- .../events/user/ApiEventChange.java | 2 +- .../events/user/ApiEventCreate.java | 2 +- .../events/user/ApiEventMigrateBilling.java | 2 +- .../user/ApiEventMigrateSubscription.java | 2 +- .../events/user/ApiEventReCreate.java | 2 +- .../events/user/ApiEventTransfer.java | 2 +- .../events/user/ApiEventUncancel.java | 2 +- .../alignment/TestPlanAligner.java | 2 +- .../TestDefaultSubscriptionTransferApi.java | 10 ++-- .../api/user/TestSubscriptionHelper.java | 2 +- .../engine/dao/MockSubscriptionDaoMemory.java | 4 +- 25 files changed, 122 insertions(+), 90 deletions(-) diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/migration/DefaultSubscriptionBaseMigrationApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/migration/DefaultSubscriptionBaseMigrationApi.java index 2fc95850ba..01746a9618 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/migration/DefaultSubscriptionBaseMigrationApi.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/migration/DefaultSubscriptionBaseMigrationApi.java @@ -255,11 +255,11 @@ private List toEvents(final DefaultSubscriptionBase defau int compForApiType(final SubscriptionBaseEvent o1, final SubscriptionBaseEvent o2, final ApiEventType type) { ApiEventType apiO1 = null; if (o1.getType() == EventType.API_USER) { - apiO1 = ((ApiEvent) o1).getEventType(); + apiO1 = ((ApiEvent) o1).getApiEventType(); } ApiEventType apiO2 = null; if (o2.getType() == EventType.API_USER) { - apiO2 = ((ApiEvent) o2).getEventType(); + apiO2 = ((ApiEvent) o2).getApiEventType(); } if (apiO1 != null && apiO1.equals(type)) { return -1; diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java index 5ec872351c..33a57d3dcb 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -119,7 +118,6 @@ public DefaultSubscriptionInternalApi(final SubscriptionDao dao, this.notificationQueueService = notificationQueueService; } - @Override public SubscriptionBase createSubscription(final UUID bundleId, final PlanPhaseSpecifier spec, final List overrides, final DateTime requestedDateWithMs, final InternalCallContext context) throws SubscriptionBaseApiException { try { @@ -509,12 +507,11 @@ public DateTime apply(final NotificationEventWithMetadata inp return input.getEffectiveDate(); } }); - } catch(NoSuchNotificationQueue noSuchNotificationQueue) { + } catch (NoSuchNotificationQueue noSuchNotificationQueue) { throw new IllegalStateException(noSuchNotificationQueue); } } - @Override public Map getNextFutureEventForSubscriptions(final SubscriptionBaseTransitionType eventType, final InternalCallContext internalCallContext) { final Iterable events = dao.getFutureEventsForAccount(internalCallContext); @@ -524,7 +521,7 @@ public boolean apply(final SubscriptionBaseEvent input) { return (eventType == SubscriptionBaseTransitionType.PHASE && input.getType() == EventType.PHASE) || input.getType() != EventType.PHASE; } }); - final Map result = filteredEvents.iterator().hasNext() ? new HashMap() : ImmutableMap.of(); + final Map result = filteredEvents.iterator().hasNext() ? new HashMap() : ImmutableMap.of(); for (SubscriptionBaseEvent cur : filteredEvents) { final DateTime targetDate = result.get(cur.getSubscriptionId()); if (targetDate == null || targetDate.compareTo(cur.getEffectiveDate()) > 0) { diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java index 5b4c7d729b..73de18abbe 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java @@ -107,7 +107,7 @@ private List toExistingEvents(final Catalog catalog, final long a case API_USER: final ApiEvent userEV = (ApiEvent) cur; - apiType = userEV.getEventType(); + apiType = userEV.getApiEventType(); planName = userEV.getEventPlan(); planPhaseName = userEV.getEventPlanPhase(); final Plan plan = (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null; diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java index a7ae3e9a48..184c3dbadd 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java @@ -414,7 +414,7 @@ public SubscriptionBaseTransitionData getTransitionFromEvent(final SubscriptionB } // Since UNCANCEL are not part of the transitions, we compute a new 'UNCANCEL' transition based on the event right before that UNCANCEL // This is used to be able to send a bus event for uncancellation - if (prev != null && event.getType() == EventType.API_USER && ((ApiEvent) event).getEventType() == ApiEventType.UNCANCEL) { + if (prev != null && event.getType() == EventType.API_USER && ((ApiEvent) event).getApiEventType() == ApiEventType.UNCANCEL) { final SubscriptionBaseTransitionData withSeq = new SubscriptionBaseTransitionData((SubscriptionBaseTransitionData) prev, EventType.API_USER, ApiEventType.UNCANCEL, seqId); return withSeq; } @@ -583,7 +583,7 @@ public void rebuildTransitions(final List inputEvents, fi case API_USER: final ApiEvent userEV = (ApiEvent) cur; - apiEventType = userEV.getEventType(); + apiEventType = userEV.getApiEventType(); isFromDisk = userEV.isFromDisk(); switch (apiEventType) { @@ -617,7 +617,7 @@ public void rebuildTransitions(final List inputEvents, fi default: throw new SubscriptionBaseError(String.format( "Unexpected UserEvent type = %s", userEV - .getEventType().toString())); + .getApiEventType().toString())); } break; default: diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java index 9bbbd32864..63d5d56883 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java @@ -65,9 +65,11 @@ import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao; import org.killbill.billing.subscription.engine.dao.model.SubscriptionEventModelDao; import org.killbill.billing.subscription.engine.dao.model.SubscriptionModelDao; +import org.killbill.billing.subscription.events.EventBaseBuilder; import org.killbill.billing.subscription.events.SubscriptionBaseEvent; import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType; import org.killbill.billing.subscription.events.phase.PhaseEvent; +import org.killbill.billing.subscription.events.phase.PhaseEventBuilder; import org.killbill.billing.subscription.events.user.ApiEvent; import org.killbill.billing.subscription.events.user.ApiEventBuilder; import org.killbill.billing.subscription.events.user.ApiEventCancel; @@ -673,7 +675,7 @@ final List reinsertFutureMigrateBillingEventOnChangeFromT } if (cur.getEffectiveDate().compareTo(migrateBillingEvent.getEffectiveDate()) > 0) { - if (cur.getType() == EventType.API_USER && ((ApiEvent) cur).getEventType() == ApiEventType.CHANGE) { + if (cur.getType() == EventType.API_USER && ((ApiEvent) cur).getApiEventType() == ApiEventType.CHANGE) { // This is an EOT change that is occurring after the MigrateBilling : returns same list return changeEvents; } @@ -690,7 +692,7 @@ final List reinsertFutureMigrateBillingEventOnChangeFromT final DateTime now = clock.getUTCNow(); final ApiEventBuilder builder = new ApiEventBuilder() .setActive(true) - .setEventType(ApiEventType.MIGRATE_BILLING) + .setApiEventType(ApiEventType.MIGRATE_BILLING) .setFromDisk(true) .setTotalOrdering(migrateBillingEvent.getTotalOrdering()) .setUuid(UUIDs.randomUUID()) @@ -942,10 +944,18 @@ private void mergeDryRunEvents(final UUID subscriptionId, final List builder) { this.totalOrdering = builder.getTotalOrdering(); @@ -87,21 +87,11 @@ public long getActiveVersion() { return activeVersion; } - @Override - public void setActiveVersion(final long activeVersion) { - this.activeVersion = activeVersion; - } - @Override public boolean isActive() { return isActive; } - @Override - public void setTotalOrdering(final long totalOrdering) { - this.totalOrdering = totalOrdering; - } - // // Really used for unit tests only as the sql implementation relies on date first and then event insertion // @@ -128,7 +118,7 @@ public int compareTo(final SubscriptionBaseEvent other) { } else if (getType() != other.getType()) { return (getType() == EventType.PHASE) ? -1 : 1; } else if (getType() == EventType.API_USER) { - return ((ApiEvent) this).getEventType().compareTo(((ApiEvent) other).getEventType()); + return ((ApiEvent) this).getApiEventType().compareTo(((ApiEvent) other).getApiEventType()); } else { return uuid.compareTo(other.getId()); } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/EventBaseBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/events/EventBaseBuilder.java index ea022cf7e0..7487b8a8f4 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/EventBaseBuilder.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/EventBaseBuilder.java @@ -22,7 +22,7 @@ import org.killbill.billing.util.UUIDs; @SuppressWarnings("unchecked") -public class EventBaseBuilder> { +public abstract class EventBaseBuilder> { private long totalOrdering; private UUID uuid; @@ -40,12 +40,26 @@ public EventBaseBuilder() { this.isActive = true; } + + public EventBaseBuilder(final SubscriptionBaseEvent event) { + this.uuid = event.getId(); + this.subscriptionId = event.getSubscriptionId(); + this.requestedDate = event.getRequestedDate(); + this.effectiveDate = event.getEffectiveDate(); + this.createdDate = event.getCreatedDate(); + this.updatedDate = event.getUpdatedDate(); + this.activeVersion = event.getActiveVersion(); + this.isActive = event.isActive(); + this.totalOrdering = event.getTotalOrdering(); + } + public EventBaseBuilder(final EventBaseBuilder copy) { this.uuid = copy.uuid; this.subscriptionId = copy.subscriptionId; this.requestedDate = copy.requestedDate; this.effectiveDate = copy.effectiveDate; this.createdDate = copy.getCreatedDate(); + this.updatedDate = copy.getUpdatedDate(); this.activeVersion = copy.activeVersion; this.isActive = copy.isActive; this.totalOrdering = copy.totalOrdering; @@ -131,4 +145,6 @@ public long getActiveVersion() { public boolean isActive() { return isActive; } + + public abstract SubscriptionBaseEvent build(); } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java index 23b5b20180..c610aa041f 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java @@ -34,12 +34,8 @@ public enum EventType { public long getTotalOrdering(); - public void setTotalOrdering(long totalOrdering); - public long getActiveVersion(); - public void setActiveVersion(long activeVersion); - public boolean isActive(); public DateTime getRequestedDate(); diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/phase/PhaseEventBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/events/phase/PhaseEventBuilder.java index 21f22e87c6..10f676f1ca 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/phase/PhaseEventBuilder.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/phase/PhaseEventBuilder.java @@ -27,6 +27,11 @@ public PhaseEventBuilder() { super(); } + public PhaseEventBuilder(final PhaseEvent phaseEvent) { + super(phaseEvent); + this.phaseName = phaseEvent.getPhase(); + } + public PhaseEventBuilder(final EventBaseBuilder base) { super(base); } @@ -39,4 +44,8 @@ public PhaseEventBuilder setPhaseName(final String phaseName) { public String getPhaseName() { return phaseName; } + + public PhaseEvent build() { + return new PhaseEventData(this); + } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEvent.java index a1bf77fa44..ea1338735a 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEvent.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEvent.java @@ -25,7 +25,7 @@ public interface ApiEvent extends SubscriptionBaseEvent { public String getEventPlanPhase(); - public ApiEventType getEventType(); + public ApiEventType getApiEventType(); public String getPriceList(); diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBase.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBase.java index 140b2a40d9..be40564af0 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBase.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBase.java @@ -20,7 +20,7 @@ public class ApiEventBase extends EventBase implements ApiEvent { - private final ApiEventType eventType; + private final ApiEventType apiEventType; // Only valid for CREATE/CHANGE private final String eventPlan; private final String eventPlanPhase; @@ -29,7 +29,7 @@ public class ApiEventBase extends EventBase implements ApiEvent { public ApiEventBase(final ApiEventBuilder builder) { super(builder); - this.eventType = builder.getEventType(); + this.apiEventType = builder.getApiEventType(); this.eventPriceList = builder.getEventPriceList(); this.eventPlan = builder.getEventPlan(); this.eventPlanPhase = builder.getEventPlanPhase(); @@ -37,8 +37,8 @@ public ApiEventBase(final ApiEventBuilder builder) { } @Override - public ApiEventType getEventType() { - return eventType; + public ApiEventType getApiEventType() { + return apiEventType; } @Override @@ -70,10 +70,10 @@ public boolean isFromDisk() { @Override public String toString() { return "ApiEventBase [ getId()= " + getId() - + " eventType=" + eventType + + " apiEventType=" + apiEventType + ", eventPlan=" + eventPlan + ", eventPlanPhase=" + eventPlanPhase - + ", getEventType()=" + getEventType() + + ", getApiEventType()=" + getApiEventType() + ", getEventPlan()=" + getEventPlan() + ", getEventPlanPhase()=" + getEventPlanPhase() + ", getType()=" + getType() diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java index 2568e767ad..18cedb6048 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java @@ -18,26 +18,33 @@ import org.killbill.billing.subscription.events.EventBaseBuilder; - public class ApiEventBuilder extends EventBaseBuilder { - private ApiEventType eventType; + private ApiEventType apiEventType; private String eventPlan; private String eventPlanPhase; private String eventPriceList; private boolean fromDisk; - public ApiEventBuilder() { super(); } + public ApiEventBuilder(final ApiEvent apiEvent) { + super(apiEvent); + this.apiEventType = apiEvent.getApiEventType(); + this.eventPlan = apiEvent.getEventPlan(); + this.eventPlanPhase = apiEvent.getEventPlanPhase(); + this.eventPriceList = apiEvent.getPriceList(); + this.fromDisk = apiEvent.isFromDisk(); + } + public ApiEventBuilder(final EventBaseBuilder base) { super(base); } - public ApiEventType getEventType() { - return eventType; + public ApiEventType getApiEventType() { + return apiEventType; } public String getEventPlan() { @@ -61,8 +68,8 @@ public ApiEventBuilder setFromDisk(final boolean fromDisk) { return this; } - public ApiEventBuilder setEventType(final ApiEventType eventType) { - this.eventType = eventType; + public ApiEventBuilder setApiEventType(final ApiEventType eventType) { + this.apiEventType = eventType; return this; } @@ -80,4 +87,30 @@ public ApiEventBuilder setEventPriceList(final String eventPriceList) { this.eventPriceList = eventPriceList; return this; } + + public ApiEventBase build() { + final ApiEventBase result; + if (apiEventType == ApiEventType.CREATE) { + result = new ApiEventCreate(this); + } else if (apiEventType == ApiEventType.RE_CREATE) { + result = new ApiEventReCreate(this); + } else if (apiEventType == ApiEventType.MIGRATE_ENTITLEMENT) { + result = new ApiEventMigrateSubscription(this); + } else if (apiEventType == ApiEventType.MIGRATE_BILLING) { + result = new ApiEventMigrateBilling(this); + } else if (apiEventType == ApiEventType.TRANSFER) { + result = new ApiEventTransfer(this); + } else if (apiEventType == ApiEventType.CHANGE) { + result = new ApiEventChange(this); + } else if (apiEventType == ApiEventType.CANCEL) { + result = new ApiEventCancel(this); + } else if (apiEventType == ApiEventType.RE_CREATE) { + result = new ApiEventReCreate(this); + } else if (apiEventType == ApiEventType.UNCANCEL) { + result = new ApiEventUncancel(this); + } else { + throw new IllegalStateException("Unknown ApiEventType " + apiEventType); + } + return result; + } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventCancel.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventCancel.java index 5634f72ca8..55c40e8c93 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventCancel.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventCancel.java @@ -20,6 +20,6 @@ public class ApiEventCancel extends ApiEventBase { public ApiEventCancel(final ApiEventBuilder builder) { - super(builder.setEventType(ApiEventType.CANCEL)); + super(builder.setApiEventType(ApiEventType.CANCEL)); } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventChange.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventChange.java index dd440fc59c..05b5878e0f 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventChange.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventChange.java @@ -20,6 +20,6 @@ public class ApiEventChange extends ApiEventBase { public ApiEventChange(final ApiEventBuilder builder) { - super(builder.setEventType(ApiEventType.CHANGE)); + super(builder.setApiEventType(ApiEventType.CHANGE)); } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventCreate.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventCreate.java index 84d856e8b2..e1b068c195 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventCreate.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventCreate.java @@ -20,6 +20,6 @@ public class ApiEventCreate extends ApiEventBase { public ApiEventCreate(final ApiEventBuilder builder) { - super(builder.setEventType(ApiEventType.CREATE)); + super(builder.setApiEventType(ApiEventType.CREATE)); } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateBilling.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateBilling.java index d4b17c16be..cc1e3a8b0b 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateBilling.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateBilling.java @@ -18,6 +18,6 @@ public class ApiEventMigrateBilling extends ApiEventBase { public ApiEventMigrateBilling(final ApiEventBuilder builder) { - super(builder.setEventType(ApiEventType.MIGRATE_BILLING)); + super(builder.setApiEventType(ApiEventType.MIGRATE_BILLING)); } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateSubscription.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateSubscription.java index 7594d91675..6194af2682 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateSubscription.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventMigrateSubscription.java @@ -19,6 +19,6 @@ public class ApiEventMigrateSubscription extends ApiEventBase { public ApiEventMigrateSubscription(final ApiEventBuilder builder) { - super(builder.setEventType(ApiEventType.MIGRATE_ENTITLEMENT)); + super(builder.setApiEventType(ApiEventType.MIGRATE_ENTITLEMENT)); } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventReCreate.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventReCreate.java index 2b1e02530c..76fe64b704 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventReCreate.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventReCreate.java @@ -19,6 +19,6 @@ public class ApiEventReCreate extends ApiEventBase { public ApiEventReCreate(final ApiEventBuilder builder) { - super(builder.setEventType(ApiEventType.RE_CREATE)); + super(builder.setApiEventType(ApiEventType.RE_CREATE)); } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventTransfer.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventTransfer.java index d432b0ae1c..a304b72c08 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventTransfer.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventTransfer.java @@ -17,7 +17,7 @@ public class ApiEventTransfer extends ApiEventBase { public ApiEventTransfer(final ApiEventBuilder builder) { - super(builder.setEventType(ApiEventType.TRANSFER)); + super(builder.setApiEventType(ApiEventType.TRANSFER)); } } diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUncancel.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUncancel.java index 5e56d95f95..5f3755c0bb 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUncancel.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUncancel.java @@ -19,6 +19,6 @@ public class ApiEventUncancel extends ApiEventBase { public ApiEventUncancel(final ApiEventBuilder builder) { - super(builder.setEventType(ApiEventType.UNCANCEL)); + super(builder.setApiEventType(ApiEventType.UNCANCEL)); } } diff --git a/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java b/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java index 75172fffd1..4d7a3e6b31 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java @@ -223,7 +223,7 @@ private SubscriptionBaseEvent createSubscriptionEvent(final DateTime effectiveDa eventBuilder.setFromDisk(true); eventBuilder.setActiveVersion(activeVersion); - return new ApiEventBase(eventBuilder.setEventType(apiEventType)); + return new ApiEventBase(eventBuilder.setApiEventType(apiEventType)); } private TimedPhase getNextTimedPhaseOnChange(final DefaultSubscriptionBase defaultSubscriptionBase, diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java index 02a39dee32..64836345ae 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java @@ -101,7 +101,7 @@ public void testEventsForCancelledSubscriptionAfterTransfer() throws Exception { Assert.assertEquals(events.size(), 1); Assert.assertEquals(events.get(0).getType(), EventType.API_USER); Assert.assertEquals(events.get(0).getEffectiveDate(), transferDate); - Assert.assertEquals(((ApiEventTransfer) events.get(0)).getEventType(), ApiEventType.TRANSFER); + Assert.assertEquals(((ApiEventTransfer) events.get(0)).getApiEventType(), ApiEventType.TRANSFER); } @Test(groups = "fast") @@ -115,7 +115,7 @@ public void testEventsAfterTransferForMigratedBundle1() throws Exception { Assert.assertEquals(events.size(), 1); Assert.assertEquals(events.get(0).getType(), EventType.API_USER); Assert.assertEquals(events.get(0).getEffectiveDate(), transferDate); - Assert.assertEquals(((ApiEventTransfer) events.get(0)).getEventType(), ApiEventType.TRANSFER); + Assert.assertEquals(((ApiEventTransfer) events.get(0)).getApiEventType(), ApiEventType.TRANSFER); } @Test(groups = "fast") @@ -129,7 +129,7 @@ public void testEventsAfterTransferForMigratedBundle2() throws Exception { Assert.assertEquals(events.size(), 1); Assert.assertEquals(events.get(0).getType(), EventType.API_USER); Assert.assertEquals(events.get(0).getEffectiveDate(), transferDate); - Assert.assertEquals(((ApiEventTransfer) events.get(0)).getEventType(), ApiEventType.TRANSFER); + Assert.assertEquals(((ApiEventTransfer) events.get(0)).getApiEventType(), ApiEventType.TRANSFER); } @Test(groups = "fast") @@ -143,7 +143,7 @@ public void testEventsAfterTransferForMigratedBundle3() throws Exception { Assert.assertEquals(events.size(), 1); Assert.assertEquals(events.get(0).getType(), EventType.API_USER); Assert.assertEquals(events.get(0).getEffectiveDate(), transferDate); - Assert.assertEquals(((ApiEventTransfer) events.get(0)).getEventType(), ApiEventType.TRANSFER); + Assert.assertEquals(((ApiEventTransfer) events.get(0)).getApiEventType(), ApiEventType.TRANSFER); } @Test(groups = "fast") @@ -157,7 +157,7 @@ public void testEventsAfterTransferForMigratedBundle4() throws Exception { Assert.assertEquals(events.size(), 1); Assert.assertEquals(events.get(0).getType(), EventType.API_USER); Assert.assertEquals(events.get(0).getEffectiveDate(), migrateSubscriptionEventEffectiveDate); - Assert.assertEquals(((ApiEventTransfer) events.get(0)).getEventType(), ApiEventType.TRANSFER); + Assert.assertEquals(((ApiEventTransfer) events.get(0)).getApiEventType(), ApiEventType.TRANSFER); } private List transferBundle(final DateTime migrateSubscriptionEventEffectiveDate, final DateTime migrateBillingEventEffectiveDate, diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java index 3034a362f6..487a3d9a79 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java @@ -125,7 +125,7 @@ public void checkNextPhaseChange(final DefaultSubscriptionBase subscription, fin assertEquals(cur.getEffectiveDate(), expPhaseChange); } else if (cur instanceof ApiEvent) { final ApiEvent uEvent = (ApiEvent) cur; - assertEquals(ApiEventType.CHANGE, uEvent.getEventType()); + assertEquals(ApiEventType.CHANGE, uEvent.getApiEventType()); assertEquals(foundChange, false); foundChange = true; } else { diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java index 6cc963b926..1d95133a6f 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java @@ -394,7 +394,7 @@ private void cancelNextChangeEvent(final UUID subscriptionId) { continue; } if (cur.getType() == EventType.API_USER && - ApiEventType.CHANGE == ((ApiEvent) cur).getEventType() && + ApiEventType.CHANGE == ((ApiEvent) cur).getApiEventType() && cur.getEffectiveDate().isAfter(clock.getUTCNow())) { it.remove(); break; @@ -416,7 +416,7 @@ public void uncancelSubscription(final DefaultSubscriptionBase subscription, fin continue; } if (cur.getType() == EventType.API_USER && - ((ApiEvent) cur).getEventType() == ApiEventType.CANCEL) { + ((ApiEvent) cur).getApiEventType() == ApiEventType.CANCEL) { it.remove(); foundCancel = true; break; From f3aad9a9fb367ee2b11d0dc9ba3108fd04598a20 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 27 Aug 2015 14:59:15 -0700 Subject: [PATCH 043/137] util: Fixes #386 Add default empty string for values required in mustache templates (to avoid throwing exceptions) --- .../billing/util/email/templates/MustacheTemplateEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/src/main/java/org/killbill/billing/util/email/templates/MustacheTemplateEngine.java b/util/src/main/java/org/killbill/billing/util/email/templates/MustacheTemplateEngine.java index b9142207e7..a55c275ae0 100644 --- a/util/src/main/java/org/killbill/billing/util/email/templates/MustacheTemplateEngine.java +++ b/util/src/main/java/org/killbill/billing/util/email/templates/MustacheTemplateEngine.java @@ -25,7 +25,7 @@ public class MustacheTemplateEngine implements TemplateEngine { @Override public String executeTemplateText(final String templateText, final Map data) { - final Template template = Mustache.compiler().compile(templateText); + final Template template = Mustache.compiler().nullValue("").compile(templateText); return template.execute(data); } } From 65d54b9789ebbea21891a4de1a559c3054793168 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 27 Aug 2015 20:36:51 -0700 Subject: [PATCH 044/137] junction: Fix major typo where recurringBillingMode ends up being null! --- .../plumbing/billing/DefaultBillingEventSet.java | 12 ++++-------- .../plumbing/billing/DefaultInternalBillingApi.java | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java index 4586db9d88..fbe8bc8aae 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java @@ -45,7 +45,7 @@ public class DefaultBillingEventSet extends TreeSet implements Sor private boolean accountAutoInvoiceOff = false; private List subscriptionIdsWithAutoInvoiceOff = new ArrayList(); - private BillingMode recurrringBillingMode; + private BillingMode recurringBillingMode; /* (non-Javadoc) * @see org.killbill.billing.junction.plumbing.billing.BillingEventSet#isAccountAutoInvoiceOff() @@ -57,7 +57,7 @@ public boolean isAccountAutoInvoiceOff() { @Override public BillingMode getRecurringBillingMode() { - return null; + return recurringBillingMode; } /* (non-Javadoc) @@ -90,12 +90,8 @@ public void setAccountAutoInvoiceIsOff(final boolean accountAutoInvoiceIsOff) { this.accountAutoInvoiceOff = accountAutoInvoiceIsOff; } - public BillingMode getRecurrringBillingMode() { - return recurrringBillingMode; - } - - public void setRecurrringBillingMode(final BillingMode recurrringBillingMode) { - this.recurrringBillingMode = recurrringBillingMode; + public void setRecurringBillingMode(final BillingMode recurringBillingMode) { + this.recurringBillingMode = recurringBillingMode; } @Override diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java index 84a3050b19..7eddd1a976 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java @@ -86,7 +86,7 @@ public BillingEventSet getBillingEventsForAccountAndUpdateAccountBCD(final UUID final List bundles = subscriptionApi.getBundlesForAccount(accountId, context); final DefaultBillingEventSet result = new DefaultBillingEventSet(); final StaticCatalog currentCatalog = catalogService.getCurrentCatalog(context); - result.setRecurrringBillingMode(currentCatalog.getRecurringBillingMode()); + result.setRecurringBillingMode(currentCatalog.getRecurringBillingMode()); try { final Account account = accountApi.getAccountById(accountId, context); From 94f91b4b25840a2ee3f917b248c9adb758d19558 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 27 Aug 2015 21:34:12 -0700 Subject: [PATCH 045/137] invoice: See #321 Initial implementation for billingMode IN_ARREAR Needs specific IN_ARREAR tests --- .../generator/BillingIntervalDetail.java | 66 ++++++++++++++++-- .../generator/DefaultInvoiceGenerator.java | 67 +++++++++---------- .../invoice/model/BillingModeGenerator.java | 3 +- ....java => DefaultBillingModeGenerator.java} | 18 ++--- .../ContiguousIntervalConsumableInArrear.java | 2 +- .../generator/TestBillingIntervalDetail.java | 25 +++---- .../model/TestInAdvanceBillingMode.java | 5 +- .../invoice/tests/ProRationTestBase.java | 13 +++- .../inAdvance/ProRationInAdvanceTestBase.java | 7 +- .../inAdvance/TestValidationProRation.java | 12 +++- 10 files changed, 143 insertions(+), 75 deletions(-) rename invoice/src/main/java/org/killbill/billing/invoice/model/{InAdvanceBillingMode.java => DefaultBillingModeGenerator.java} (91%) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java index 2d95f1fa1a..2f8edca4a7 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java @@ -17,7 +17,7 @@ package org.killbill.billing.invoice.generator; import org.joda.time.LocalDate; - +import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; import com.google.common.annotations.VisibleForTesting; @@ -29,17 +29,23 @@ public class BillingIntervalDetail { private final LocalDate targetDate; private final int billingCycleDay; private final BillingPeriod billingPeriod; - + private final BillingMode billingMode; private LocalDate firstBillingCycleDate; private LocalDate effectiveEndDate; private LocalDate lastBillingCycleDate; - public BillingIntervalDetail(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) { + public BillingIntervalDetail(final LocalDate startDate, + final LocalDate endDate, + final LocalDate targetDate, + final int billingCycleDay, + final BillingPeriod billingPeriod, + final BillingMode billingMode) { this.startDate = startDate; this.endDate = endDate; this.targetDate = targetDate; this.billingCycleDay = billingCycleDay; this.billingPeriod = billingPeriod; + this.billingMode = billingMode; computeAll(); } @@ -61,6 +67,11 @@ public LocalDate getLastBillingCycleDate() { return lastBillingCycleDate; } + public boolean hasSomethingToBill() { + return effectiveEndDate != null /* IN_ARREAR mode prior we have reached our firstBillingCycleDate */ && + (endDate == null || endDate.isAfter(startDate)); /* When there is an endDate, it should be > startDate since we don't bill for less than a day */ + } + private void computeAll() { calculateFirstBillingCycleDate(); calculateEffectiveEndDate(); @@ -87,6 +98,44 @@ void calculateFirstBillingCycleDate() { } private void calculateEffectiveEndDate() { + if (billingMode == BillingMode.IN_ADVANCE) { + calculateInAdvanceEffectiveEndDate(); + } else { + calculateInArrearEffectiveEndDate(); + } + } + + private void calculateInArrearEffectiveEndDate() { + if (targetDate.isBefore(firstBillingCycleDate)) { + // Nothing to bill for, hasSomethingToBill will return false + effectiveEndDate = null; + return; + } + + if (endDate != null && endDate.isBefore(firstBillingCycleDate)) { + effectiveEndDate = endDate; + return; + } + + final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths(); + int numberOfPeriods = 0; + LocalDate proposedDate = firstBillingCycleDate; + + while (proposedDate.isBefore(targetDate)) { + proposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod); + numberOfPeriods += 1; + } + proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay); + + // The proposedDate is greater to our endDate => return it + if (endDate != null && endDate.isBefore(proposedDate)) { + effectiveEndDate = endDate; + } else { + effectiveEndDate = proposedDate; + } + } + + private void calculateInAdvanceEffectiveEndDate() { // We have an endDate and the targetDate is greater or equal to our endDate => return it if (endDate != null && !targetDate.isBefore(endDate)) { @@ -117,9 +166,13 @@ private void calculateEffectiveEndDate() { } } - private void calculateLastBillingCycleDate() { + if (effectiveEndDate == null) { + lastBillingCycleDate = firstBillingCycleDate; + return; + } + // Start from firstBillingCycleDate and billingPeriod until we pass the effectiveEndDate LocalDate proposedDate = firstBillingCycleDate; int numberOfPeriods = 0; @@ -134,13 +187,12 @@ private void calculateLastBillingCycleDate() { if (proposedDate.isBefore(firstBillingCycleDate)) { // Make sure not to go too far in the past - lastBillingCycleDate = firstBillingCycleDate; + lastBillingCycleDate = firstBillingCycleDate; } else { - lastBillingCycleDate = proposedDate; + lastBillingCycleDate = proposedDate; } } - // // We start from a billCycleDate // diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index 1bc6c58ce0..af96f5dd26 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -47,7 +47,7 @@ import org.killbill.billing.invoice.model.BillingModeGenerator; import org.killbill.billing.invoice.model.DefaultInvoice; import org.killbill.billing.invoice.model.FixedPriceInvoiceItem; -import org.killbill.billing.invoice.model.InAdvanceBillingMode; +import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.InvalidDateSequenceException; import org.killbill.billing.invoice.model.RecurringInvoiceItem; import org.killbill.billing.invoice.model.RecurringInvoiceItemData; @@ -57,7 +57,6 @@ import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear; import org.killbill.billing.junction.BillingEvent; import org.killbill.billing.junction.BillingEventSet; -import org.killbill.billing.usage.RawUsage; import org.killbill.billing.util.config.InvoiceConfig; import org.killbill.billing.util.currency.KillBillMoney; import org.killbill.clock.Clock; @@ -79,12 +78,14 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator { private final Clock clock; private final InvoiceConfig config; private final RawUsageOptimizer rawUsageOptimizer; + final BillingModeGenerator billingModeGenerator; @Inject public DefaultInvoiceGenerator(final Clock clock, final InvoiceConfig config, final RawUsageOptimizer rawUsageOptimizer) { this.clock = clock; this.config = config; this.rawUsageOptimizer = rawUsageOptimizer; + this.billingModeGenerator = new DefaultBillingModeGenerator(); } /* @@ -105,7 +106,7 @@ public Invoice generateInvoice(final Account account, @Nullable final BillingEve final Invoice invoice = new DefaultInvoice(account.getId(), new LocalDate(clock.getUTCNow(), account.getTimeZone()), adjustedTargetDate, targetCurrency); final UUID invoiceId = invoice.getId(); - final List inAdvanceItems = generateInAdvanceInvoiceItems(account.getId(), invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency); + final List inAdvanceItems = generateFixedAndRecurringInvoiceItems(account.getId(), invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency); invoice.addInvoiceItems(inAdvanceItems); final List usageItems = generateUsageConsumableInArrearItems(account, invoiceId, events, existingInvoices, targetDate, context); @@ -209,9 +210,9 @@ public boolean apply(final InvoiceItem input) { return result; } - private List generateInAdvanceInvoiceItems(final UUID accountId, final UUID invoiceId, final BillingEventSet eventSet, - @Nullable final List existingInvoices, final LocalDate targetDate, - final Currency targetCurrency) throws InvoiceApiException { + private List generateFixedAndRecurringInvoiceItems(final UUID accountId, final UUID invoiceId, final BillingEventSet eventSet, + @Nullable final List existingInvoices, final LocalDate targetDate, + final Currency targetCurrency) throws InvoiceApiException { final AccountItemTree accountItemTree = new AccountItemTree(accountId, invoiceId); if (existingInvoices != null) { for (final Invoice invoice : existingInvoices) { @@ -226,7 +227,9 @@ private List generateInAdvanceInvoiceItems(final UUID accountId, fi } // Generate list of proposed invoice items based on billing events from junction-- proposed items are ALL items since beginning of time - final List proposedItems = generateInAdvanceInvoiceItems(invoiceId, accountId, eventSet, targetDate, targetCurrency); + final List proposedItems = new ArrayList(); + generateRecurringInvoiceItems(invoiceId, accountId, eventSet, targetDate, targetCurrency, proposedItems); + processFixedPriceEvents(invoiceId, accountId, eventSet, targetDate, targetCurrency, proposedItems); accountItemTree.mergeWithProposedItems(proposedItems); return accountItemTree.getResultingItemList(); @@ -255,12 +258,10 @@ private LocalDate adjustTargetDate(final List existingInvoices, final L return maxDate; } - private List generateInAdvanceInvoiceItems(final UUID invoiceId, final UUID accountId, final BillingEventSet events, - final LocalDate targetDate, final Currency currency) throws InvoiceApiException { - final List items = new ArrayList(); - + private List generateRecurringInvoiceItems(final UUID invoiceId, final UUID accountId, final BillingEventSet events, + final LocalDate targetDate, final Currency currency, final List proposedItems) throws InvoiceApiException { if (events.size() == 0) { - return items; + return proposedItems; } // Pretty-print the generated invoice items from the junction events @@ -277,32 +278,39 @@ private List generateInAdvanceInvoiceItems(final UUID invoiceId, fi if (!events.getSubscriptionIdsWithAutoInvoiceOff(). contains(thisEvent.getSubscription().getId())) { // don't consider events for subscriptions that have auto_invoice_off final BillingEvent adjustedNextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null; - items.addAll(processInAdvanceEvents(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder)); + proposedItems.addAll(processRecurringEvents(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode())); } } - items.addAll(processInAdvanceEvents(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder)); + proposedItems.addAll(processRecurringEvents(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode())); log.info(logStringBuilder.toString()); - return items; + return proposedItems; } + private List processFixedPriceEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List proposedItems) { + final Iterator eventIt = events.iterator(); + while (eventIt.hasNext()) { + final BillingEvent thisEvent = eventIt.next(); + + final InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, accountId, thisEvent, targetDate, currency); + if (fixedPriceInvoiceItem != null) { + proposedItems.add(fixedPriceInvoiceItem); + } + } + return proposedItems; + } + + // Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day) - private List processInAdvanceEvents(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, + private List processRecurringEvents(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, final LocalDate targetDate, final Currency currency, - final StringBuilder logStringBuilder) throws InvoiceApiException { + final StringBuilder logStringBuilder, final BillingMode billingMode) throws InvoiceApiException { final List items = new ArrayList(); - // Handle fixed price items - final InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, accountId, thisEvent, targetDate, currency); - if (fixedPriceInvoiceItem != null) { - items.add(fixedPriceInvoiceItem); - } - // Handle recurring items final BillingPeriod billingPeriod = thisEvent.getBillingPeriod(); if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) { - final BillingModeGenerator billingModeGenerator = instantiateBillingMode(thisEvent.getBillingMode()); final LocalDate startDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); if (!startDate.isAfter(targetDate)) { @@ -312,7 +320,7 @@ private List processInAdvanceEvents(final UUID invoiceId, final UUI final List itemData; try { - itemData = billingModeGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod); + itemData = billingModeGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode); } catch (InvalidDateSequenceException e) { throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate); } @@ -348,15 +356,6 @@ private List processInAdvanceEvents(final UUID invoiceId, final UUI return items; } - private BillingModeGenerator instantiateBillingMode(final BillingMode billingMode) { - switch (billingMode) { - case IN_ADVANCE: - return new InAdvanceBillingMode(); - default: - throw new UnsupportedOperationException(); - } - } - InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, final LocalDate targetDate, final Currency currency) { final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java index 4116f3c1ed..26fbfd90b7 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java @@ -23,10 +23,11 @@ import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; public interface BillingModeGenerator { List generateInvoiceItemData(LocalDate startDate, @Nullable LocalDate endDate, LocalDate targetDate, - int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException; + int billingCycleDay, BillingPeriod billingPeriod, BillingMode billingMode) throws InvalidDateSequenceException; } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java similarity index 91% rename from invoice/src/main/java/org/killbill/billing/invoice/model/InAdvanceBillingMode.java rename to invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java index 31663ef39d..cb8f6f88db 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/InAdvanceBillingMode.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java @@ -23,24 +23,26 @@ import javax.annotation.Nullable; import org.joda.time.LocalDate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.generator.BillingIntervalDetail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateNumberOfWholeBillingPeriods; import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateProRationAfterLastBillingCycleDate; import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod; -public class InAdvanceBillingMode implements BillingModeGenerator { +public class DefaultBillingModeGenerator implements BillingModeGenerator { - private static final Logger log = LoggerFactory.getLogger(InAdvanceBillingMode.class); + private static final Logger log = LoggerFactory.getLogger(DefaultBillingModeGenerator.class); @Override public List generateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate, final LocalDate targetDate, - final int billingCycleDayLocal, final BillingPeriod billingPeriod) throws InvalidDateSequenceException { + final int billingCycleDayLocal, + final BillingPeriod billingPeriod, + final BillingMode billingMode) throws InvalidDateSequenceException { if (endDate != null && endDate.isBefore(startDate)) { throw new InvalidDateSequenceException(); } @@ -50,10 +52,10 @@ public List generateInvoiceItemData(final LocalDate st final List results = new ArrayList(); - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, billingMode); // We are not billing for less than a day (we could...) - if (endDate != null && endDate.equals(startDate)) { + if (!billingIntervalDetail.hasSomethingToBill()) { return results; } // diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java index f1747c2ab7..a7d78e4456 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java @@ -106,7 +106,7 @@ public ContiguousIntervalConsumableInArrear build(final boolean closedInterval) } final LocalDate endDate = closedInterval ? new LocalDate(billingEvents.get(billingEvents.size() - 1).getEffectiveDate(), getAccountTimeZone()) : targetDate; - final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, endDate, targetDate, getBCD(), usage.getBillingPeriod()); + final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, endDate, targetDate, getBCD(), usage.getBillingPeriod(), usage.getBillingMode()); int numberOfPeriod = 0; // First billingCycleDate prior startDate diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestBillingIntervalDetail.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestBillingIntervalDetail.java index 9941eb98f5..8021c2cba2 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestBillingIntervalDetail.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestBillingIntervalDetail.java @@ -17,6 +17,7 @@ package org.killbill.billing.invoice.generator; import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; import org.testng.Assert; import org.testng.annotations.Test; @@ -35,7 +36,7 @@ public class TestBillingIntervalDetail extends InvoiceTestSuiteNoDB { public void testCalculateFirstBillingCycleDate1() throws Exception { final LocalDate from = new LocalDate("2012-01-16"); final int bcd = 17; - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE); billingIntervalDetail.calculateFirstBillingCycleDate(); Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-17")); } @@ -50,7 +51,7 @@ public void testCalculateFirstBillingCycleDate1() throws Exception { public void testCalculateFirstBillingCycleDate2() throws Exception { final LocalDate from = new LocalDate("2012-02-16"); final int bcd = 30; - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE); billingIntervalDetail.calculateFirstBillingCycleDate(); Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29")); } @@ -69,7 +70,7 @@ public void testCalculateFirstBillingCycleDate2() throws Exception { public void testCalculateFirstBillingCycleDate4() throws Exception { final LocalDate from = new LocalDate("2012-01-31"); final int bcd = 30; - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.MONTHLY); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE); billingIntervalDetail.calculateFirstBillingCycleDate(); Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29")); } @@ -84,7 +85,7 @@ public void testCalculateFirstBillingCycleDate4() throws Exception { public void testCalculateFirstBillingCycleDate3() throws Exception { final LocalDate from = new LocalDate("2012-02-16"); final int bcd = 14; - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE); billingIntervalDetail.calculateFirstBillingCycleDate(); Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2013-02-14")); } @@ -92,7 +93,7 @@ public void testCalculateFirstBillingCycleDate3() throws Exception { @Test(groups = "fast") public void testNextBCDShouldNotBeInThePast() throws Exception { final LocalDate from = new LocalDate("2012-07-16"); - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), 15, BillingPeriod.MONTHLY); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), 15, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE); final LocalDate to = billingIntervalDetail.getFirstBillingCycleDate(); Assert.assertEquals(to, new LocalDate("2012-08-15")); } @@ -100,7 +101,7 @@ public void testNextBCDShouldNotBeInThePast() throws Exception { @Test(groups = "fast") public void testBeforeBCDWithOnOrAfter() throws Exception { final LocalDate from = new LocalDate("2012-03-02"); - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), 3, BillingPeriod.MONTHLY); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), 3, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE); final LocalDate to = billingIntervalDetail.getFirstBillingCycleDate(); Assert.assertEquals(to, new LocalDate("2012-03-03")); } @@ -108,7 +109,7 @@ public void testBeforeBCDWithOnOrAfter() throws Exception { @Test(groups = "fast") public void testEqualBCDWithOnOrAfter() throws Exception { final LocalDate from = new LocalDate("2012-03-03"); - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), 3, BillingPeriod.MONTHLY); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), 3, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE); final LocalDate to = billingIntervalDetail.getFirstBillingCycleDate(); Assert.assertEquals(to, new LocalDate("2012-03-03")); } @@ -116,7 +117,7 @@ public void testEqualBCDWithOnOrAfter() throws Exception { @Test(groups = "fast") public void testAfterBCDWithOnOrAfter() throws Exception { final LocalDate from = new LocalDate("2012-03-04"); - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), 3, BillingPeriod.MONTHLY); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), 3, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE); final LocalDate to = billingIntervalDetail.getFirstBillingCycleDate(); Assert.assertEquals(to, new LocalDate("2012-04-03")); } @@ -127,7 +128,7 @@ public void testEffectiveEndDate() throws Exception { final LocalDate targetDate = new LocalDate(2012, 8, 16); final BillingPeriod billingPeriod = BillingPeriod.MONTHLY; - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(firstBCD, null, targetDate, 16, billingPeriod); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(firstBCD, null, targetDate, 16, billingPeriod, BillingMode.IN_ADVANCE); final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate(); Assert.assertEquals(effectiveEndDate, new LocalDate(2012, 9, 16)); } @@ -138,7 +139,7 @@ public void testLastBCD() throws Exception { final LocalDate endDate = new LocalDate(2012, 9, 15); // so we get effectiveEndDate on 9/15 final LocalDate targetDate = new LocalDate(2012, 8, 16); - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, endDate, targetDate, 16, BillingPeriod.MONTHLY); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, endDate, targetDate, 16, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE); final LocalDate lastBCD = billingIntervalDetail.getLastBillingCycleDate(); Assert.assertEquals(lastBCD, new LocalDate(2012, 8, 16)); } @@ -148,7 +149,7 @@ public void testLastBCDShouldNotBeBeforePreviousBCD() throws Exception { final LocalDate start = new LocalDate("2012-07-16"); final int bcdLocal = 15; - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, start, bcdLocal, BillingPeriod.MONTHLY); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, start, bcdLocal, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE); final LocalDate lastBCD = billingIntervalDetail.getLastBillingCycleDate(); Assert.assertEquals(lastBCD, new LocalDate("2012-08-15")); } @@ -160,7 +161,7 @@ public void testBCD31StartingWith30DayMonth() throws Exception { final LocalDate end = null; final int bcdLocal = 31; - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcdLocal, BillingPeriod.MONTHLY); + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcdLocal, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE); final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate(); Assert.assertEquals(effectiveEndDate, new LocalDate("2012-05-31")); } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java index c5c35b90f6..db121f60e1 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java @@ -23,6 +23,7 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; import org.testng.Assert; import org.testng.annotations.Test; @@ -148,9 +149,9 @@ public void testCalculateSimpleInvoiceItemWithBCDAfterStartDayWithTargetDateIn3M private void verifyInvoiceItems(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, final DateTimeZone dateTimeZone, final int billingCycleDayLocal, final BillingPeriod billingPeriod, final LinkedHashMap expectedDates) throws InvalidDateSequenceException { - final InAdvanceBillingMode billingMode = new InAdvanceBillingMode(); + final DefaultBillingModeGenerator billingMode = new DefaultBillingModeGenerator(); - final List invoiceItems = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod); + final List invoiceItems = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ADVANCE); int i = 0; for (final LocalDate periodStartDate : expectedDates.keySet()) { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/ProRationTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/tests/ProRationTestBase.java index 4ce38af394..e86af76494 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/ProRationTestBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/tests/ProRationTestBase.java @@ -23,9 +23,11 @@ import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.InvoiceTestSuiteNoDB; import org.killbill.billing.invoice.model.BillingModeGenerator; +import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.InvalidDateSequenceException; import org.killbill.billing.invoice.model.RecurringInvoiceItemData; @@ -34,10 +36,15 @@ public abstract class ProRationTestBase extends InvoiceTestSuiteNoDB { - protected abstract BillingModeGenerator getBillingMode(); + protected BillingModeGenerator getBillingModeGenerator() { + return new DefaultBillingModeGenerator(); + } protected abstract BillingPeriod getBillingPeriod(); + protected abstract BillingMode getBillingMode(); + + protected void testCalculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate targetDate, final int billingCycleDay, final BigDecimal expectedValue) throws InvalidDateSequenceException { try { final BigDecimal numberOfBillingCycles; @@ -65,7 +72,7 @@ protected void testCalculateNumberOfBillingCycles(final LocalDate startDate, fin } protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException { - final List items = getBillingMode().generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod()); + final List items = getBillingModeGenerator().generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); BigDecimal numberOfBillingCycles = ZERO; for (final RecurringInvoiceItemData item : items) { @@ -76,7 +83,7 @@ protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, f } protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException { - final List items = getBillingMode().generateInvoiceItemData(startDate, null, targetDate, billingCycleDay, getBillingPeriod()); + final List items = getBillingModeGenerator().generateInvoiceItemData(startDate, null, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); BigDecimal numberOfBillingCycles = ZERO; for (final RecurringInvoiceItemData item : items) { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java index 6fd8e15649..4572c5b649 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java @@ -16,14 +16,13 @@ package org.killbill.billing.invoice.tests.inAdvance; -import org.killbill.billing.invoice.model.BillingModeGenerator; -import org.killbill.billing.invoice.model.InAdvanceBillingMode; +import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.invoice.tests.ProRationTestBase; public abstract class ProRationInAdvanceTestBase extends ProRationTestBase { @Override - protected BillingModeGenerator getBillingMode() { - return new InAdvanceBillingMode(); + protected BillingMode getBillingMode() { + return BillingMode.IN_ADVANCE; } } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/TestValidationProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/TestValidationProRation.java index 741316e494..eeec015da3 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/TestValidationProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/TestValidationProRation.java @@ -17,11 +17,12 @@ package org.killbill.billing.invoice.tests.inAdvance; import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.invoice.model.BillingModeGenerator; import org.testng.annotations.Test; import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.invoice.model.InAdvanceBillingMode; +import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.InvalidDateSequenceException; import org.killbill.billing.invoice.tests.ProRationTestBase; @@ -35,8 +36,13 @@ protected BillingPeriod getBillingPeriod() { } @Override - protected BillingModeGenerator getBillingMode() { - return new InAdvanceBillingMode(); + protected BillingMode getBillingMode() { + return BillingMode.IN_ADVANCE; + } + + @Override + protected BillingModeGenerator getBillingModeGenerator() { + return new DefaultBillingModeGenerator(); } @Test(groups = "fast", expectedExceptions = InvalidDateSequenceException.class) From 25e55cc36c0f29230b2df9047dd5bf50cc64c390 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sat, 29 Aug 2015 09:49:50 -0700 Subject: [PATCH 046/137] invoice: See #321 Add unit test for IN_ARREAR recurring BillingMode Refactor existing tests Fix an issue /BillingIntervalDetail Add new test TestInArrearBillingMode --- .../generator/BillingIntervalDetail.java | 7 +- .../invoice/TestDefaultInvoicePaymentApi.java | 4 +- .../model/TestInArrearBillingMode.java | 167 ++++++++++++++++++ .../InternationalPriceMock.java | 2 +- .../InvoiceTestUtils.java | 3 +- .../ProRationTestBase.java | 2 +- .../inAdvance/GenericProRationTestBase.java | 2 +- .../inAdvance/ProRationInAdvanceTestBase.java | 4 +- .../inAdvance/TestValidationProRation.java | 4 +- .../annual/GenericProRationTests.java | 4 +- .../inAdvance/annual/TestDoubleProRation.java | 4 +- .../annual/TestLeadingProRation.java | 4 +- .../inAdvance/annual/TestProRation.java | 5 +- .../annual/TestTrailingProRation.java | 4 +- .../monthly/GenericProRationTests.java | 4 +- .../monthly/TestDoubleProRation.java | 4 +- .../monthly/TestLeadingProRation.java | 4 +- .../inAdvance/monthly/TestProRation.java | 4 +- .../monthly/TestTrailingProRation.java | 4 +- .../quarterly/GenericProRationTests.java | 4 +- .../quarterly/TestDoubleProRation.java | 4 +- .../quarterly/TestLeadingProRation.java | 4 +- .../inAdvance/quarterly/TestProRation.java | 5 +- .../quarterly/TestTrailingProRation.java | 4 +- 24 files changed, 212 insertions(+), 45 deletions(-) create mode 100644 invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/InternationalPriceMock.java (96%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/InvoiceTestUtils.java (98%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/ProRationTestBase.java (98%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/GenericProRationTestBase.java (99%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/ProRationInAdvanceTestBase.java (87%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/TestValidationProRation.java (96%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/annual/GenericProRationTests.java (87%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/annual/TestDoubleProRation.java (98%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/annual/TestLeadingProRation.java (97%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/annual/TestProRation.java (94%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/annual/TestTrailingProRation.java (96%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/monthly/GenericProRationTests.java (87%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/monthly/TestDoubleProRation.java (97%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/monthly/TestLeadingProRation.java (97%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/monthly/TestProRation.java (98%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/monthly/TestTrailingProRation.java (96%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/quarterly/GenericProRationTests.java (87%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/quarterly/TestDoubleProRation.java (97%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/quarterly/TestLeadingProRation.java (97%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/quarterly/TestProRation.java (98%) rename invoice/src/test/java/org/killbill/billing/invoice/{tests => proRations}/inAdvance/quarterly/TestTrailingProRation.java (96%) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java index 2f8edca4a7..513cd870ef 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java @@ -120,9 +120,12 @@ private void calculateInArrearEffectiveEndDate() { final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths(); int numberOfPeriods = 0; LocalDate proposedDate = firstBillingCycleDate; + LocalDate nextProposedDate = proposedDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod); - while (proposedDate.isBefore(targetDate)) { - proposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod); + + while (!nextProposedDate.isAfter(targetDate)) { + proposedDate = nextProposedDate; + nextProposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod); numberOfPeriods += 1; } proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay); diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java index 5f9477d5f2..0af10180aa 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java @@ -33,8 +33,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import static org.killbill.billing.invoice.tests.InvoiceTestUtils.createAndPersistInvoice; -import static org.killbill.billing.invoice.tests.InvoiceTestUtils.createAndPersistPayment; +import static org.killbill.billing.invoice.proRations.InvoiceTestUtils.createAndPersistInvoice; +import static org.killbill.billing.invoice.proRations.InvoiceTestUtils.createAndPersistPayment; public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java new file mode 100644 index 0000000000..aa4733e117 --- /dev/null +++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java @@ -0,0 +1,167 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.model; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.List; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; +import org.killbill.billing.catalog.api.BillingPeriod; +import org.killbill.billing.invoice.InvoiceTestSuiteNoDB; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TestInArrearBillingMode extends InvoiceTestSuiteNoDB { + + private static final DateTimeZone TIMEZONE = DateTimeZone.forID("Pacific/Pitcairn"); + private static final BillingPeriod BILLING_PERIOD = BillingPeriod.MONTHLY; + + @Test(groups = "fast") + public void testItemShouldNotStartInThePast() throws Exception { + final LocalDate startDate = new LocalDate(2012, 7, 16); + final LocalDate endDate = new LocalDate(2012, 7, 16); + final LocalDate targetDate = new LocalDate(2012, 7, 16); + final int billingCycleDayLocal = 15; + + final LinkedHashMap expectedDates = new LinkedHashMap(); + verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + } + + @Test(groups = "fast") + public void testCalculateSimpleInvoiceItemWithNoEndDate() throws Exception { + final LocalDate startDate = new LocalDate(new DateTime("2012-07-17T02:25:33.000Z", DateTimeZone.UTC), TIMEZONE); + final LocalDate endDate = null; + final LocalDate targetDate = new LocalDate(2012, 7, 16); + final int billingCycleDayLocal = 15; + + final LinkedHashMap expectedDates = new LinkedHashMap(); + verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + } + + @Test(groups = "fast") + public void testCalculateSimpleInvoiceItemWithBCDBeforeStartDay() throws Exception { + final LocalDate startDate = new LocalDate(2012, 7, 16); + final LocalDate endDate = new LocalDate(2012, 8, 16); + final LocalDate targetDate = new LocalDate(2012, 7, 16); + final int billingCycleDayLocal = 15; + + final LinkedHashMap expectedDates = new LinkedHashMap(); + verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + + final LocalDate targetDate2 = new LocalDate(2012, 8, 15); + expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15)); + verifyInvoiceItems(startDate, endDate, targetDate2, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + } + + @Test(groups = "fast") + public void testCalculateSimpleInvoiceItemWithBCDEqualsStartDay() throws Exception { + final LocalDate startDate = new LocalDate(2012, 7, 16); + final LocalDate endDate = new LocalDate(2012, 8, 16); + final LocalDate targetDate = new LocalDate(2012, 7, 16); + final int billingCycleDayLocal = 16; + + final LinkedHashMap expectedDates = new LinkedHashMap(); + verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + + expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 16)); + final LocalDate targetDate2 = new LocalDate(2012, 8, 16); + verifyInvoiceItems(startDate, endDate, targetDate2, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + + } + + @Test(groups = "fast") + public void testCalculateSimpleInvoiceItemWithBCDAfterStartDay() throws Exception { + final LocalDate startDate = new LocalDate(2012, 7, 16); + final LocalDate endDate = new LocalDate(2012, 8, 16); + final LocalDate targetDate = new LocalDate(2012, 7, 16); + final int billingCycleDayLocal = 17; + + final LinkedHashMap expectedDates = new LinkedHashMap(); + verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + + final LocalDate targetDate2 = new LocalDate(2012, 7, 17); + expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 17)); + verifyInvoiceItems(startDate, endDate, targetDate2, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + } + + @Test(groups = "fast") + public void testCalculateSimpleInvoiceItemWithBCDBeforeStartDayWithTargetDateIn3Months() throws Exception { + final LocalDate startDate = new LocalDate(2012, 7, 16); + final LocalDate endDate = null; + final LocalDate targetDate = new LocalDate(2012, 10, 16); + final int billingCycleDayLocal = 15; + + final LinkedHashMap expectedDates = new LinkedHashMap(); + expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15)); + expectedDates.put(new LocalDate(2012, 8, 15), new LocalDate(2012, 9, 15)); + expectedDates.put(new LocalDate(2012, 9, 15), new LocalDate(2012, 10, 15)); + + verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + } + + @Test(groups = "fast") + public void testCalculateSimpleInvoiceItemWithBCDEqualsStartDayWithTargetDateIn3Months() throws Exception { + final LocalDate startDate = new LocalDate(2012, 7, 16); + final LocalDate endDate = null; + final LocalDate targetDate = new LocalDate(2012, 10, 16); + final int billingCycleDayLocal = 16; + + final LinkedHashMap expectedDates = new LinkedHashMap(); + expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 16)); + expectedDates.put(new LocalDate(2012, 8, 16), new LocalDate(2012, 9, 16)); + expectedDates.put(new LocalDate(2012, 9, 16), new LocalDate(2012, 10, 16)); + + verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + } + + @Test(groups = "fast") + public void testCalculateSimpleInvoiceItemWithBCDAfterStartDayWithTargetDateIn3Months() throws Exception { + final LocalDate startDate = new LocalDate(2012, 7, 16); + final LocalDate endDate = null; + final LocalDate targetDate = new LocalDate(2012, 10, 16); + final int billingCycleDayLocal = 17; + + final LinkedHashMap expectedDates = new LinkedHashMap(); + expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 17)); + expectedDates.put(new LocalDate(2012, 7, 17), new LocalDate(2012, 8, 17)); + expectedDates.put(new LocalDate(2012, 8, 17), new LocalDate(2012, 9, 17)); + + verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + } + + private void verifyInvoiceItems(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, + final DateTimeZone dateTimeZone, final int billingCycleDayLocal, final BillingPeriod billingPeriod, + final LinkedHashMap expectedDates) throws InvalidDateSequenceException { + final DefaultBillingModeGenerator billingMode = new DefaultBillingModeGenerator(); + + final List invoiceItems = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ARREAR); + + int i = 0; + for (final LocalDate periodStartDate : expectedDates.keySet()) { + Assert.assertEquals(invoiceItems.get(i).getStartDate(), periodStartDate); + Assert.assertEquals(invoiceItems.get(i).getEndDate(), expectedDates.get(periodStartDate)); + Assert.assertTrue(invoiceItems.get(0).getNumberOfCycles().compareTo(BigDecimal.ONE) <= 0); + i++; + } + Assert.assertEquals(invoiceItems.size(), i); + } +} diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/InternationalPriceMock.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InternationalPriceMock.java similarity index 96% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/InternationalPriceMock.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/InternationalPriceMock.java index 803c38dafe..d604794151 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/InternationalPriceMock.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InternationalPriceMock.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests; +package org.killbill.billing.invoice.proRations; import java.math.BigDecimal; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java similarity index 98% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java index 67bf3920e4..1833d6b636 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java @@ -16,14 +16,13 @@ * under the License. */ -package org.killbill.billing.invoice.tests; +package org.killbill.billing.invoice.proRations; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/ProRationTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java similarity index 98% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/ProRationTestBase.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java index e86af76494..82e110aac2 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/ProRationTestBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests; +package org.killbill.billing.invoice.proRations; import static org.killbill.billing.invoice.TestInvoiceHelper.*; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/GenericProRationTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java similarity index 99% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/GenericProRationTestBase.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java index 8c3774d40d..c05b8fe7f4 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/GenericProRationTestBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance; +package org.killbill.billing.invoice.proRations.inAdvance; import static org.killbill.billing.invoice.TestInvoiceHelper.*; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/ProRationInAdvanceTestBase.java similarity index 87% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/ProRationInAdvanceTestBase.java index 4572c5b649..6ae7a219c6 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/ProRationInAdvanceTestBase.java @@ -14,10 +14,10 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance; +package org.killbill.billing.invoice.proRations.inAdvance; import org.killbill.billing.catalog.api.BillingMode; -import org.killbill.billing.invoice.tests.ProRationTestBase; +import org.killbill.billing.invoice.proRations.ProRationTestBase; public abstract class ProRationInAdvanceTestBase extends ProRationTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/TestValidationProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/TestValidationProRation.java similarity index 96% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/TestValidationProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/TestValidationProRation.java index eeec015da3..471b9f0d66 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/TestValidationProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/TestValidationProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance; +package org.killbill.billing.invoice.proRations.inAdvance; import org.joda.time.LocalDate; import org.killbill.billing.catalog.api.BillingMode; @@ -24,7 +24,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.ProRationTestBase; +import org.killbill.billing.invoice.proRations.ProRationTestBase; import static org.testng.Assert.assertEquals; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/GenericProRationTests.java similarity index 87% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/GenericProRationTests.java index ee2cec5dc3..d6573c6e94 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/GenericProRationTests.java @@ -14,14 +14,14 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.annual; +package org.killbill.billing.invoice.proRations.inAdvance.annual; import static org.killbill.billing.invoice.TestInvoiceHelper.*; import java.math.BigDecimal; import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.invoice.tests.inAdvance.GenericProRationTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.GenericProRationTestBase; public class GenericProRationTests extends GenericProRationTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestDoubleProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestDoubleProRation.java similarity index 98% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestDoubleProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestDoubleProRation.java index ffb2501c19..24c6fc89a7 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestDoubleProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestDoubleProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.annual; +package org.killbill.billing.invoice.proRations.inAdvance.annual; import java.math.BigDecimal; @@ -23,7 +23,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; import static org.killbill.billing.invoice.TestInvoiceHelper.FOURTEEN; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestLeadingProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestLeadingProRation.java similarity index 97% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestLeadingProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestLeadingProRation.java index ffed530012..341efba15c 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestLeadingProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestLeadingProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.annual; +package org.killbill.billing.invoice.proRations.inAdvance.annual; import static org.killbill.billing.invoice.TestInvoiceHelper.*; @@ -25,7 +25,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestLeadingProRation extends ProRationInAdvanceTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestProRation.java similarity index 94% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestProRation.java index 49a42fe7db..e56b43fa43 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestProRation.java @@ -14,19 +14,18 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.annual; +package org.killbill.billing.invoice.proRations.inAdvance.annual; import static org.killbill.billing.invoice.TestInvoiceHelper.*; import java.math.BigDecimal; -import org.joda.time.Days; import org.joda.time.LocalDate; import org.testng.annotations.Test; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestProRation extends ProRationInAdvanceTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestTrailingProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestTrailingProRation.java similarity index 96% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestTrailingProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestTrailingProRation.java index fe245ecf63..487710c5c4 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/annual/TestTrailingProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestTrailingProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.annual; +package org.killbill.billing.invoice.proRations.inAdvance.annual; import static org.killbill.billing.invoice.TestInvoiceHelper.*; @@ -25,7 +25,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestTrailingProRation extends ProRationInAdvanceTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/GenericProRationTests.java similarity index 87% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/GenericProRationTests.java index 735812589b..edad4c578f 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/GenericProRationTests.java @@ -14,14 +14,14 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.monthly; +package org.killbill.billing.invoice.proRations.inAdvance.monthly; import static org.killbill.billing.invoice.TestInvoiceHelper.*; import java.math.BigDecimal; import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.invoice.tests.inAdvance.GenericProRationTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.GenericProRationTestBase; public class GenericProRationTests extends GenericProRationTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestDoubleProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestDoubleProRation.java similarity index 97% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestDoubleProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestDoubleProRation.java index 64583aee33..ab66e7baa1 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestDoubleProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestDoubleProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.monthly; +package org.killbill.billing.invoice.proRations.inAdvance.monthly; import static org.killbill.billing.invoice.TestInvoiceHelper.*; @@ -25,7 +25,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestDoubleProRation extends ProRationInAdvanceTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestLeadingProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestLeadingProRation.java similarity index 97% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestLeadingProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestLeadingProRation.java index 53ab598bf3..2b492ea5c8 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestLeadingProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestLeadingProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.monthly; +package org.killbill.billing.invoice.proRations.inAdvance.monthly; import static org.killbill.billing.invoice.TestInvoiceHelper.*; @@ -25,7 +25,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestLeadingProRation extends ProRationInAdvanceTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestProRation.java similarity index 98% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestProRation.java index a4bd726736..87ae8027d5 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.monthly; +package org.killbill.billing.invoice.proRations.inAdvance.monthly; import java.math.BigDecimal; @@ -23,7 +23,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; import static org.killbill.billing.invoice.TestInvoiceHelper.EIGHT; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestTrailingProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestTrailingProRation.java similarity index 96% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestTrailingProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestTrailingProRation.java index 36ccd424df..8c0f667b2c 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/monthly/TestTrailingProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/monthly/TestTrailingProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.monthly; +package org.killbill.billing.invoice.proRations.inAdvance.monthly; import java.math.BigDecimal; @@ -23,7 +23,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; import static org.killbill.billing.invoice.TestInvoiceHelper.EIGHT; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/GenericProRationTests.java similarity index 87% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/GenericProRationTests.java index 31363993bd..513f73662d 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/GenericProRationTests.java @@ -14,14 +14,14 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.quarterly; +package org.killbill.billing.invoice.proRations.inAdvance.quarterly; import static org.killbill.billing.invoice.TestInvoiceHelper.*; import java.math.BigDecimal; import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.invoice.tests.inAdvance.GenericProRationTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.GenericProRationTestBase; public class GenericProRationTests extends GenericProRationTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestDoubleProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestDoubleProRation.java similarity index 97% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestDoubleProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestDoubleProRation.java index a6dd313ae9..4489758f05 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestDoubleProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestDoubleProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.quarterly; +package org.killbill.billing.invoice.proRations.inAdvance.quarterly; import static org.killbill.billing.invoice.TestInvoiceHelper.*; @@ -25,7 +25,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestDoubleProRation extends ProRationInAdvanceTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestLeadingProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestLeadingProRation.java similarity index 97% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestLeadingProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestLeadingProRation.java index 140144f3f6..230ad69942 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestLeadingProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestLeadingProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.quarterly; +package org.killbill.billing.invoice.proRations.inAdvance.quarterly; import static org.killbill.billing.invoice.TestInvoiceHelper.*; @@ -25,7 +25,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestLeadingProRation extends ProRationInAdvanceTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestProRation.java similarity index 98% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestProRation.java index 531573ce5f..cad222a980 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestProRation.java @@ -14,19 +14,18 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.quarterly; +package org.killbill.billing.invoice.proRations.inAdvance.quarterly; import static org.killbill.billing.invoice.TestInvoiceHelper.*; import java.math.BigDecimal; -import org.joda.time.Days; import org.joda.time.LocalDate; import org.testng.annotations.Test; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestProRation extends ProRationInAdvanceTestBase { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestTrailingProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestTrailingProRation.java similarity index 96% rename from invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestTrailingProRation.java rename to invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestTrailingProRation.java index 32a47eb17a..ec2b0364d0 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/tests/inAdvance/quarterly/TestTrailingProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/quarterly/TestTrailingProRation.java @@ -14,7 +14,7 @@ * under the License. */ -package org.killbill.billing.invoice.tests.inAdvance.quarterly; +package org.killbill.billing.invoice.proRations.inAdvance.quarterly; import static org.killbill.billing.invoice.TestInvoiceHelper.*; @@ -25,7 +25,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase; +import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase; import org.killbill.billing.util.currency.KillBillMoney; public class TestTrailingProRation extends ProRationInAdvanceTestBase { From 68e8376a199bd15772816fd1f594e863aacb517c Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sun, 30 Aug 2015 17:18:10 -0700 Subject: [PATCH 047/137] invoice: Refactor the computation of next invoice future notitications Prior the change, the code would compute those future notifications after having generated the invoice. The code was cumbersome because some of the info to compute those date were not easily accessible. Also, this does not work for when we will need to implement the future notification date in the recurring IN_ARREAR because the information would only be accessible deep inside the invoice computation logic. --- .../billing/invoice/InvoiceDispatcher.java | 228 ++++++++++-------- .../generator/DefaultInvoiceGenerator.java | 96 ++++++-- .../invoice/generator/InvoiceGenerator.java | 2 +- .../generator/InvoiceWithMetadata.java | 87 +++++++ .../model/DefaultBillingModeGenerator.java | 2 +- .../invoice/TestInvoiceDispatcher.java | 42 +--- .../billing/invoice/dao/TestInvoiceDao.java | 37 ++- .../TestDefaultInvoiceGenerator.java | 121 ++++++---- 8 files changed, 383 insertions(+), 232 deletions(-) create mode 100644 invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java index d229093938..6f4b247304 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java @@ -68,6 +68,8 @@ import org.killbill.billing.invoice.dao.InvoiceModelDao; import org.killbill.billing.invoice.generator.BillingIntervalDetail; import org.killbill.billing.invoice.generator.InvoiceGenerator; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; import org.killbill.billing.invoice.model.DefaultInvoice; import org.killbill.billing.invoice.model.FixedPriceInvoiceItem; import org.killbill.billing.invoice.model.InvoiceItemFactory; @@ -203,7 +205,7 @@ private Invoice processSubscriptionInternal(final UUID subscriptionId, final Dat } } - public Invoice processAccount(final UUID accountId, final DateTime targetDate, + public Invoice processAccount(final UUID accountId, @Nullable final DateTime targetDate, @Nullable final DryRunArguments dryRunArguments, final InternalCallContext context) throws InvoiceApiException { GlobalLock lock = null; try { @@ -226,12 +228,14 @@ private Invoice processAccountWithLock(final UUID accountId, @Nullable final Dat @Nullable final DryRunArguments dryRunArguments, final InternalCallContext context) throws InvoiceApiException { final boolean isDryRun = dryRunArguments != null; - // inputTargetDateTime is only allowed in dryRun mode to have the system compute it + // A null inputTargetDateTime is only allowed in dryRun mode to have the system compute it Preconditions.checkArgument(inputTargetDateTime != null || isDryRun, "inputTargetDateTime is required in non dryRun mode"); try { // Make sure to first set the BCD if needed then get the account object (to have the BCD set) final BillingEventSet billingEvents = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId, dryRunArguments, context); - + if (billingEvents.isEmpty()) { + return null; + } final List candidateDateTimes = (inputTargetDateTime != null) ? ImmutableList.of(inputTargetDateTime) : getUpcomingInvoiceCandidateDates(context); for (final DateTime curTargetDateTime : candidateDateTimes) { final Invoice invoice = processAccountWithLockAndInputTargetDate(accountId, curTargetDateTime, billingEvents, isDryRun, context); @@ -250,9 +254,7 @@ private Invoice processAccountWithLockAndInputTargetDate(final UUID accountId, f final BillingEventSet billingEvents, final boolean isDryRun, final InternalCallContext context) throws InvoiceApiException { try { final Account account = accountApi.getAccountById(accountId, context); - final DateAndTimeZoneContext dateAndTimeZoneContext = billingEvents.iterator().hasNext() ? - new DateAndTimeZoneContext(billingEvents.iterator().next().getEffectiveDate(), account.getTimeZone(), clock) : - null; + final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(billingEvents.iterator().next().getEffectiveDate(), account.getTimeZone(), clock); final List invoices = billingEvents.isAccountAutoInvoiceOff() ? ImmutableList.of() : @@ -265,20 +267,30 @@ public Invoice apply(final InvoiceModelDao input) { })); final Currency targetCurrency = account.getCurrency(); + final LocalDate targetDate = dateAndTimeZoneContext.computeTargetDate(targetDateTime); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, billingEvents, invoices, targetDate, targetCurrency, context); + final Invoice invoice = invoiceWithMetadata.getInvoice(); + + // Compute future notifications + final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, billingEvents, dateAndTimeZoneContext, context); - final LocalDate targetDate = (dateAndTimeZoneContext != null && targetDateTime != null) ? dateAndTimeZoneContext.computeTargetDate(targetDateTime) : null; - final Invoice invoice = targetDate != null ? generator.generateInvoice(account, billingEvents, invoices, targetDate, targetCurrency, context) : null; // + // If invoice comes back null, there is nothing new to generate, we can bail early // if (invoice == null) { - log.info("Generated null invoice for accountId {} and targetDate {} (targetDateTime {})", new Object[]{accountId, targetDate, targetDateTime}); - if (!isDryRun) { + if (isDryRun) { + log.info("Generated null dryRun invoice for accountId {} and targetDate {} (targetDateTime {})", new Object[]{accountId, targetDate, targetDateTime}); + } else { + log.info("Generated null invoice for accountId {} and targetDate {} (targetDateTime {})", new Object[]{accountId, targetDate, targetDateTime}); + final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(), context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()); + + commitInvoiceAndSetFutureNotifications(account, null, ImmutableList.of(), futureAccountNotifications, false, context); postEvent(event, accountId, context); } - return invoice; + return null; } // Generate missing credit (> 0 for generation and < 0 for use) prior we call the plugin @@ -286,14 +298,39 @@ public Invoice apply(final InvoiceModelDao input) { if (cbaItem != null) { invoice.addInvoiceItem(cbaItem); } - // // Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice // final CallContext callContext = buildCallContext(context); invoice.addInvoiceItems(invoicePluginDispatcher.getAdditionalInvoiceItems(invoice, callContext)); + + + if (!isDryRun) { - commitInvoiceStateAndNotifyAccountIfConfigured(account, invoice, billingEvents, dateAndTimeZoneContext, targetDate, context); + + // Compute whether this is a new invoice object (or just some adjustments on an existing invoice), and extract invoiceIds for later use + final Set uniqueInvoiceIds = getUniqueInvoiceIds(invoice); + final boolean isRealInvoiceWithItems = uniqueInvoiceIds.remove(invoice.getId()); + final Set adjustedUniqueOtherInvoiceId = uniqueInvoiceIds; + + logInvoiceWithItems(account, invoice, targetDate, adjustedUniqueOtherInvoiceId, isRealInvoiceWithItems); + + // Transformation to Invoice -> InvoiceModelDao + final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice); + final Iterable invoiceItemModelDaos = transformToInvoiceModelDao(invoice.getInvoiceItems()); + + // Commit invoice on disk + final boolean isThereAnyItemsLeft = commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, invoiceItemModelDaos, futureAccountNotifications, isRealInvoiceWithItems, context); + + final boolean isRealInvoiceWithNonEmptyItems = isThereAnyItemsLeft ? isRealInvoiceWithItems : false; + + + setChargedThroughDates(dateAndTimeZoneContext, invoice.getInvoiceItems(FixedPriceInvoiceItem.class), invoice.getInvoiceItems(RecurringInvoiceItem.class), context); + + // TODO we should send bus events when we commit the ionvoice on disk in commitInvoice + postEvents(account, invoice, adjustedUniqueOtherInvoiceId, isRealInvoiceWithNonEmptyItems, context); + + notifyAccountIfEnabled(account, invoice, isRealInvoiceWithNonEmptyItems, context); } return invoice; } catch (final AccountApiException e) { @@ -305,38 +342,88 @@ public Invoice apply(final InvoiceModelDao input) { } } - private void commitInvoiceStateAndNotifyAccountIfConfigured(final Account account, final Invoice invoice, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext, final LocalDate targetDate, final InternalCallContext context) throws SubscriptionBaseApiException, InvoiceApiException { - boolean isRealInvoiceWithNonEmptyItems = false; - // Extract the set of invoiceId for which we see items that don't belong to current generated invoice - final Set adjustedUniqueOtherInvoiceId = new TreeSet(); - adjustedUniqueOtherInvoiceId.addAll(Collections2.transform(invoice.getInvoiceItems(), new Function() { + + private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext, final InternalCallContext context) { + + final Map> result = new HashMap>(); + + for (final UUID subscriptionId : invoiceWithMetadata.getPerSubscriptionFutureNotificationDates().keySet()) { + + final List perSubscriptionNotifications = new ArrayList(); + + final SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = invoiceWithMetadata.getPerSubscriptionFutureNotificationDates().get(subscriptionId); + // Add next recurring date if any + if (subscriptionFutureNotificationDates.getNextRecurringDate() != null) { + perSubscriptionNotifications.add(new SubscriptionNotification(dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(subscriptionFutureNotificationDates.getNextRecurringDate()), true)); + } + // Add next usage dates if any + if (subscriptionFutureNotificationDates.getNextUsageDates() != null) { + for (String usageName : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) { + final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageName); + final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageName, nextNotificationDateForUsage, dateAndTimeZoneContext, billingEvents); + perSubscriptionNotifications.add(new SubscriptionNotification(subscriptionUsageCallbackDate, true)); + } + } + if (!perSubscriptionNotifications.isEmpty()) { + result.put(subscriptionId, perSubscriptionNotifications); + } + } + + // If dryRunNotification is enabled we also need to fetch the upcoming PHASE dates (we add SubscriptionNotification with isForInvoiceNotificationTrigger = false) + final boolean isInvoiceNotificationEnabled = invoiceConfig.getDryRunNotificationSchedule().getMillis() > 0; + if (isInvoiceNotificationEnabled) { + final Map upcomingPhasesForSubscriptions = subscriptionApi.getNextFutureEventForSubscriptions(SubscriptionBaseTransitionType.PHASE, context); + for (UUID cur : upcomingPhasesForSubscriptions.keySet()) { + final DateTime curDate = upcomingPhasesForSubscriptions.get(cur); + List resultValue = result.get(cur); + if (resultValue == null) { + resultValue = new ArrayList(); + } + resultValue.add(new SubscriptionNotification(curDate, false)); + result.put(cur, resultValue); + } + } + return new FutureAccountNotifications(dateAndTimeZoneContext, result); + } + + private Iterable transformToInvoiceModelDao(final List invoiceItems) { + return Iterables.transform(invoiceItems, + new Function() { + @Override + public InvoiceItemModelDao apply(final InvoiceItem input) { + return new InvoiceItemModelDao(input); + } + }); + } + + private Set getUniqueInvoiceIds(final Invoice invoice) { + final Set uniqueInvoiceIds = new TreeSet(); + uniqueInvoiceIds.addAll(Collections2.transform(invoice.getInvoiceItems(), new Function() { @Nullable @Override public UUID apply(@Nullable final InvoiceItem input) { return input.getInvoiceId(); } })); - boolean isRealInvoiceWithItems = adjustedUniqueOtherInvoiceId.remove(invoice.getId()); + return uniqueInvoiceIds; + } + + private void logInvoiceWithItems(final Account account, final Invoice invoice, final LocalDate targetDate, final Set adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithItems) { if (isRealInvoiceWithItems) { log.info("Generated invoice {} with {} items for accountId {} and targetDate {}", new Object[]{invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate}); } else { final Joiner joiner = Joiner.on(","); final String adjustedInvoices = joiner.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()])); log.info("Adjusting existing invoices {} with {} items for accountId {} and targetDate {})", new Object[]{adjustedInvoices, invoice.getNumberOfItems(), - account.getId(), targetDate}); + account.getId(), targetDate}); } + } - // Transformation to Invoice -> InvoiceModelDao - final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice); - final Iterable invoiceItemModelDaos = Iterables.transform(invoice.getInvoiceItems(), - new Function() { - @Override - public InvoiceItemModelDao apply(final InvoiceItem input) { - return new InvoiceItemModelDao(input); - } - }); - final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceItemModelDaos, billingEvents, dateAndTimeZoneContext, context); + private boolean commitInvoiceAndSetFutureNotifications(final Account account, final InvoiceModelDao invoiceModelDao, + final Iterable invoiceItemModelDaos, + final FutureAccountNotifications futureAccountNotifications, + boolean isRealInvoiceWithItems, final InternalCallContext context) throws SubscriptionBaseApiException, InvoiceApiException { // We filter any zero amount for USAGE items prior we generate the invoice, which may leave us with an invoice with no items; // we recompute the isRealInvoiceWithItems flag based on what is left (the call to invoice is still necessary to set the future notifications). final Iterable filteredInvoiceItemModelDaos = Iterables.filter(invoiceItemModelDaos, new Predicate() { @@ -347,17 +434,15 @@ public boolean apply(@Nullable final InvoiceItemModelDao input) { }); final boolean isThereAnyItemsLeft = filteredInvoiceItemModelDaos.iterator().hasNext(); - isRealInvoiceWithNonEmptyItems = isThereAnyItemsLeft ? isRealInvoiceWithItems : false; - if (isThereAnyItemsLeft) { invoiceDao.createInvoice(invoiceModelDao, ImmutableList.copyOf(filteredInvoiceItemModelDaos), isRealInvoiceWithItems, futureAccountNotifications, context); } else { invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications, context); } + return isThereAnyItemsLeft; + } - final List fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class); - final List recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class); - setChargedThroughDates(dateAndTimeZoneContext, fixedPriceInvoiceItems, recurringInvoiceItems, context); + private void postEvents(final Account account, final Invoice invoice, final Set adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithNonEmptyItems, final InternalCallContext context) { final List events = new ArrayList(); if (isRealInvoiceWithNonEmptyItems) { @@ -370,11 +455,12 @@ public boolean apply(@Nullable final InvoiceItemModelDao input) { context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()); events.add(event); } - for (final InvoiceInternalEvent event : events) { postEvent(event, account.getId(), context); } + } + private void notifyAccountIfEnabled(final Account account, final Invoice invoice, final boolean isRealInvoiceWithNonEmptyItems, final InternalCallContext context) throws InvoiceApiException { if (account.isNotifiedForInvoices() && isRealInvoiceWithNonEmptyItems) { // Need to re-hydrate the invoice object to get the invoice number (record id) // API_FIX InvoiceNotifier public API? @@ -382,6 +468,7 @@ public boolean apply(@Nullable final InvoiceItemModelDao input) { } } + private InvoiceItem computeCBAOnExistingInvoice(final Invoice invoice, final InternalCallContext context) throws InvoiceApiException { // Transformation to Invoice -> InvoiceModelDao final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice); @@ -405,75 +492,6 @@ private CallContext buildCallContext(final InternalCallContext context) { return internalCallContextFactory.createCallContext(context); } - @VisibleForTesting - FutureAccountNotifications createNextFutureNotificationDate(final Iterable invoiceItems, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext, final InternalCallContext context) { - - final Map> result = new HashMap>(); - - final Map perSubscriptionUsage = new HashMap(); - - // For each subscription that has a positive (amount) recurring item, create the date - // at which we should be called back for next invoice. - // - for (final InvoiceItemModelDao item : invoiceItems) { - - List perSubscriptionCallback = result.get(item.getSubscriptionId()); - if (perSubscriptionCallback == null && (item.getType() == InvoiceItemType.RECURRING || item.getType() == InvoiceItemType.USAGE)) { - perSubscriptionCallback = new ArrayList(); - result.put(item.getSubscriptionId(), perSubscriptionCallback); - } - - switch (item.getType()) { - case RECURRING: - if ((item.getEndDate() != null) && - (item.getAmount() == null || - item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) { - perSubscriptionCallback.add(new SubscriptionNotification(dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(item.getEndDate()), true)); - } - break; - - case USAGE: - final String key = item.getSubscriptionId().toString() + ":" + item.getUsageName(); - final LocalDate perSubscriptionUsageRecurringDate = perSubscriptionUsage.get(key); - if (perSubscriptionUsageRecurringDate == null || perSubscriptionUsageRecurringDate.compareTo(item.getEndDate()) < 0) { - perSubscriptionUsage.put(key, item.getEndDate()); - } - break; - - default: - // Ignore - } - } - - for (final String key : perSubscriptionUsage.keySet()) { - final String[] parts = key.split(":"); - final UUID subscriptionId = UUID.fromString(parts[0]); - - final List perSubscriptionCallback = result.get(subscriptionId); - final String usageName = parts[1]; - final LocalDate endDate = perSubscriptionUsage.get(key); - - final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageName, endDate, dateAndTimeZoneContext, billingEvents); - perSubscriptionCallback.add(new SubscriptionNotification(subscriptionUsageCallbackDate, true)); - } - - // If dryRunNotification is enabled we also need to fetch the upcoming PHASE dates (we add SubscriptionNotification with isForInvoiceNotificationTrigger = false) - final boolean isInvoiceNotificationEnabled = invoiceConfig.getDryRunNotificationSchedule().getMillis() > 0; - if (isInvoiceNotificationEnabled) { - final Map upcomingPhasesForSubscriptions = subscriptionApi.getNextFutureEventForSubscriptions(SubscriptionBaseTransitionType.PHASE, context); - for (UUID cur : upcomingPhasesForSubscriptions.keySet()) { - final DateTime curDate = upcomingPhasesForSubscriptions.get(cur); - List resultValue = result.get(cur); - if (resultValue == null) { - resultValue = new ArrayList(); - } - resultValue.add(new SubscriptionNotification(curDate, false)); - result.put(cur, resultValue); - } - } - return new FutureAccountNotifications(dateAndTimeZoneContext, result); - } - private DateTime getNextUsageBillingDate(final UUID subscriptionId, final String usageName, final LocalDate chargedThroughDate, final DateAndTimeZoneContext dateAndTimeZoneContext, final BillingEventSet billingEvents) { final Usage usage = billingEvents.getUsages().get(usageName); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index af96f5dd26..070348df23 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -44,10 +44,11 @@ import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItem; import org.killbill.billing.invoice.api.InvoiceItemType; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; import org.killbill.billing.invoice.model.BillingModeGenerator; +import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.DefaultInvoice; import org.killbill.billing.invoice.model.FixedPriceInvoiceItem; -import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.InvalidDateSequenceException; import org.killbill.billing.invoice.model.RecurringInvoiceItem; import org.killbill.billing.invoice.model.RecurringInvoiceItemData; @@ -92,12 +93,12 @@ public DefaultInvoiceGenerator(final Clock clock, final InvoiceConfig config, fi * adjusts target date to the maximum invoice target date, if future invoices exist */ @Override - public Invoice generateInvoice(final Account account, @Nullable final BillingEventSet events, - @Nullable final List existingInvoices, - final LocalDate targetDate, - final Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException { + public InvoiceWithMetadata generateInvoice(final Account account, @Nullable final BillingEventSet events, + @Nullable final List existingInvoices, + final LocalDate targetDate, + final Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException { if ((events == null) || (events.size() == 0) || events.isAccountAutoInvoiceOff()) { - return null; + return new InvoiceWithMetadata(null, ImmutableMap.of()); } validateTargetDate(targetDate); @@ -105,21 +106,25 @@ public Invoice generateInvoice(final Account account, @Nullable final BillingEve final Invoice invoice = new DefaultInvoice(account.getId(), new LocalDate(clock.getUTCNow(), account.getTimeZone()), adjustedTargetDate, targetCurrency); final UUID invoiceId = invoice.getId(); + final Map perSubscriptionFutureNotificationDates = new HashMap(); - final List inAdvanceItems = generateFixedAndRecurringInvoiceItems(account.getId(), invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency); - invoice.addInvoiceItems(inAdvanceItems); + final List fixedAndRecurringItems = generateFixedAndRecurringInvoiceItems(account.getId(), invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates); + invoice.addInvoiceItems(fixedAndRecurringItems); - final List usageItems = generateUsageConsumableInArrearItems(account, invoiceId, events, existingInvoices, targetDate, context); + final List usageItems = generateUsageConsumableInArrearItems(account, invoiceId, events, existingInvoices, targetDate, perSubscriptionFutureNotificationDates, context); invoice.addInvoiceItems(usageItems); - return invoice.getInvoiceItems().size() != 0 ? invoice : null; + return new InvoiceWithMetadata(invoice.getInvoiceItems().isEmpty() ? null : invoice, perSubscriptionFutureNotificationDates); } private List generateUsageConsumableInArrearItems(final Account account, final UUID invoiceId, final BillingEventSet eventSet, @Nullable final List existingInvoices, final LocalDate targetDate, + final Map perSubscriptionFutureNotificationDates, final InternalCallContext internalCallContext) throws InvoiceApiException { + final Map perSubscriptionUsage = new HashMap(); + final Map> perSubscriptionConsumableInArrearUsageItems = extractPerSubscriptionExistingConsumableInArrearUsageItems(eventSet.getUsages(), existingInvoices); try { final List items = Lists.newArrayList(); @@ -157,7 +162,11 @@ public boolean apply(@Nullable final Usage input) { if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) { final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); - items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of())); + + final List newProposedItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + items.addAll(newProposedItems); + updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newProposedItems, perSubscriptionFutureNotificationDates); + curEvents = Lists.newArrayList(); } curSubscriptionId = subscriptionId; @@ -166,7 +175,10 @@ public boolean apply(@Nullable final Usage input) { if (curSubscriptionId != null) { final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); - items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of())); + + final List newProposedItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + items.addAll(newProposedItems); + updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newProposedItems, perSubscriptionFutureNotificationDates); } return items; @@ -175,6 +187,19 @@ public boolean apply(@Nullable final Usage input) { } } + + private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final List newProposedItems, Map perSubscriptionFutureNotificationDates) { + for (final InvoiceItem item : newProposedItems) { + SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); + if (subscriptionFutureNotificationDates == null) { + subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(); + perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); + } + subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(item.getUsageName(), item.getEndDate()); + } + } + + private Map> extractPerSubscriptionExistingConsumableInArrearUsageItems(final Map knownUsage, @Nullable final List existingInvoices) { if (existingInvoices == null || existingInvoices.isEmpty()) { @@ -212,7 +237,7 @@ public boolean apply(final InvoiceItem input) { private List generateFixedAndRecurringInvoiceItems(final UUID accountId, final UUID invoiceId, final BillingEventSet eventSet, @Nullable final List existingInvoices, final LocalDate targetDate, - final Currency targetCurrency) throws InvoiceApiException { + final Currency targetCurrency, Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { final AccountItemTree accountItemTree = new AccountItemTree(accountId, invoiceId); if (existingInvoices != null) { for (final Invoice invoice : existingInvoices) { @@ -228,8 +253,8 @@ private List generateFixedAndRecurringInvoiceItems(final UUID accou // Generate list of proposed invoice items based on billing events from junction-- proposed items are ALL items since beginning of time final List proposedItems = new ArrayList(); - generateRecurringInvoiceItems(invoiceId, accountId, eventSet, targetDate, targetCurrency, proposedItems); - processFixedPriceEvents(invoiceId, accountId, eventSet, targetDate, targetCurrency, proposedItems); + processRecurringBillingEvents(invoiceId, accountId, eventSet, targetDate, targetCurrency, proposedItems, perSubscriptionFutureNotificationDate); + processFixedBillingEvents(invoiceId, accountId, eventSet, targetDate, targetCurrency, proposedItems); accountItemTree.mergeWithProposedItems(proposedItems); return accountItemTree.getResultingItemList(); @@ -258,8 +283,10 @@ private LocalDate adjustTargetDate(final List existingInvoices, final L return maxDate; } - private List generateRecurringInvoiceItems(final UUID invoiceId, final UUID accountId, final BillingEventSet events, - final LocalDate targetDate, final Currency currency, final List proposedItems) throws InvoiceApiException { + private List processRecurringBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, + final LocalDate targetDate, final Currency currency, final List proposedItems, + Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { + if (events.size() == 0) { return proposedItems; } @@ -278,17 +305,39 @@ private List generateRecurringInvoiceItems(final UUID invoiceId, fi if (!events.getSubscriptionIdsWithAutoInvoiceOff(). contains(thisEvent.getSubscription().getId())) { // don't consider events for subscriptions that have auto_invoice_off final BillingEvent adjustedNextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null; - proposedItems.addAll(processRecurringEvents(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode())); + final List newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode()); + proposedItems.addAll(newProposedItems); + updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), newProposedItems, perSubscriptionFutureNotificationDate); } } - proposedItems.addAll(processRecurringEvents(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode())); + final List newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode()); + proposedItems.addAll(newProposedItems); + updatePerSubscriptionNextNotificationDate(nextEvent.getSubscription().getId(), newProposedItems, perSubscriptionFutureNotificationDate); log.info(logStringBuilder.toString()); return proposedItems; } - private List processFixedPriceEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List proposedItems) { + private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final List newProposedItems, Map perSubscriptionFutureNotificationDates) { + for (final InvoiceItem item : newProposedItems) { + if ((item.getEndDate() != null) && + (item.getAmount() == null || + item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) { + SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); + if (subscriptionFutureNotificationDates == null) { + subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(); + perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); + } + subscriptionFutureNotificationDates.updateNextRecurringDateIfRequired(item.getEndDate()); + } + } + } + + + + + private List processFixedBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List proposedItems) { final Iterator eventIt = events.iterator(); while (eventIt.hasNext()) { final BillingEvent thisEvent = eventIt.next(); @@ -301,11 +350,10 @@ private List processFixedPriceEvents(final UUID invoiceId, final UU return proposedItems; } - // Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day) - private List processRecurringEvents(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, - final LocalDate targetDate, final Currency currency, - final StringBuilder logStringBuilder, final BillingMode billingMode) throws InvoiceApiException { + private List processRecurringEvent(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, + final LocalDate targetDate, final Currency currency, + final StringBuilder logStringBuilder, final BillingMode billingMode) throws InvoiceApiException { final List items = new ArrayList(); // Handle recurring items diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java index 8023ee9f29..3ff29a6e24 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java @@ -32,6 +32,6 @@ public interface InvoiceGenerator { - public Invoice generateInvoice(Account account, @Nullable BillingEventSet events, @Nullable List existingInvoices, + public InvoiceWithMetadata generateInvoice(Account account, @Nullable BillingEventSet events, @Nullable List existingInvoices, LocalDate targetDate, Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException; } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java new file mode 100644 index 0000000000..a3192453e3 --- /dev/null +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java @@ -0,0 +1,87 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.generator; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.joda.time.LocalDate; +import org.killbill.billing.invoice.api.Invoice; + +public class InvoiceWithMetadata { + + private final Invoice invoice; + private final Map perSubscriptionFutureNotificationDates; + + public static class SubscriptionFutureNotificationDates { + + private LocalDate nextRecurringDate; + private Map nextUsageDates; + + public SubscriptionFutureNotificationDates() { + this.nextRecurringDate = null; + this.nextUsageDates = null; + } + + public void updateNextRecurringDateIfRequired(final LocalDate nextRecurringDateCandidate) { + nextRecurringDate = getMaxDate(nextRecurringDate, nextRecurringDateCandidate); + } + + public void updateNextUsageDateIfRequired(final String usageName, final LocalDate nextUsageDateCandidate) { + if (nextUsageDates == null) { + nextUsageDates = new HashMap(); + } + final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageName), nextUsageDateCandidate); + nextUsageDates.put(usageName, nextUsageDate); + } + + public LocalDate getNextRecurringDate() { + return nextRecurringDate; + } + + public Map getNextUsageDates() { + return nextUsageDates; + } + + private static LocalDate getMaxDate(@Nullable final LocalDate existingDate, final LocalDate nextDateCandidate) { + if (existingDate == null) { + return nextDateCandidate; + } else { + return nextDateCandidate.compareTo(existingDate) > 0 ? nextDateCandidate : existingDate; + } + } + + } + + public InvoiceWithMetadata(final Invoice invoice, final Map perSubscriptionFutureNotificationDates) { + this.invoice = invoice; + this.perSubscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates; + } + + public Invoice getInvoice() { + return invoice; + } + + public Map getPerSubscriptionFutureNotificationDates() { + return perSubscriptionFutureNotificationDates; + } + +} diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java index cb8f6f88db..29bcc1f7f9 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java @@ -54,7 +54,7 @@ public List generateInvoiceItemData(final LocalDate st final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, billingMode); - // We are not billing for less than a day (we could...) + // We are not billing for less than a day if (!billingIntervalDetail.hasSomethingToBill()) { return results; } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java index 5929b0b9ff..2bdde603db 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java @@ -19,12 +19,10 @@ package org.killbill.billing.invoice; import java.math.BigDecimal; -import java.util.Collections; import java.util.List; import java.util.UUID; import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; @@ -38,8 +36,6 @@ import org.killbill.billing.catalog.api.PhaseType; import org.killbill.billing.catalog.api.Plan; import org.killbill.billing.catalog.api.PlanPhase; -import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications; -import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification; import org.killbill.billing.invoice.TestInvoiceHelper.DryRunFutureDateArguments; import org.killbill.billing.invoice.api.DryRunArguments; import org.killbill.billing.invoice.api.Invoice; @@ -47,14 +43,11 @@ import org.killbill.billing.invoice.api.InvoiceItem; import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.invoice.api.InvoiceNotifier; -import org.killbill.billing.invoice.dao.InvoiceItemModelDao; import org.killbill.billing.invoice.dao.InvoiceModelDao; import org.killbill.billing.invoice.notification.NullInvoiceNotifier; import org.killbill.billing.junction.BillingEventSet; import org.killbill.billing.subscription.api.SubscriptionBase; import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.util.timezone.DateAndTimeZoneContext; -import org.killbill.clock.ClockMock; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.BeforeMethod; @@ -82,7 +75,7 @@ public void testDryRunInvoice() throws InvoiceApiException, AccountApiException, final BillingEventSet events = new MockBillingEventSet(); final Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD(); final PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen(); - final DateTime effectiveDate = new DateTime().minusDays(1); + final DateTime effectiveDate = clock.getUTCNow().minusDays(1); final Currency currency = Currency.USD; final BigDecimal fixedPrice = null; events.add(invoiceUtil.createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase, @@ -91,7 +84,7 @@ public void testDryRunInvoice() throws InvoiceApiException, AccountApiException, Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(events); - final DateTime target = new DateTime(); + final DateTime target = effectiveDate; final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier(); final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao, @@ -190,35 +183,4 @@ public void testWithOverdueEvents() throws Exception { Assert.assertEquals(item.getSubscriptionId(), subscription.getId()); } } - - @Test(groups = "slow") - public void testCreateNextFutureNotificationDate() throws Exception { - - final LocalDate startDate = new LocalDate("2012-10-26"); - final LocalDate endDate = new LocalDate("2012-11-26"); - - ((ClockMock) clock).setTime(new DateTime(2012, 10, 13, 1, 12, 23, DateTimeZone.UTC)); - - final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(clock.getUTCNow(), DateTimeZone.forID("Pacific/Pitcairn"), clock); - - final InvoiceItemModelDao item = new InvoiceItemModelDao(UUID.randomUUID(), clock.getUTCNow(), InvoiceItemType.RECURRING, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), - null, "planName", "phaseName", null, startDate, endDate, new BigDecimal("23.9"), new BigDecimal("23.9"), Currency.EUR, null); - - final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier(); - final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao, - internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(), - null, invoiceConfig, clock); - - final FutureAccountNotifications futureAccountNotifications = dispatcher.createNextFutureNotificationDate(Collections.singletonList(item), null, dateAndTimeZoneContext, context); - - Assert.assertEquals(futureAccountNotifications.getNotifications().size(), 1); - - final List receivedDates = futureAccountNotifications.getNotifications().get(item.getSubscriptionId()); - Assert.assertEquals(receivedDates.size(), 1); - - final LocalDate receivedTargetDate = new LocalDate(receivedDates.get(0).getEffectiveDate(), DateTimeZone.forID("Pacific/Pitcairn")); - Assert.assertEquals(receivedTargetDate, endDate); - - Assert.assertTrue(receivedDates.get(0).getEffectiveDate().compareTo(new DateTime(2012, 11, 27, 1, 12, 23, DateTimeZone.UTC)) <= 0); - } } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java index 97cd232dfd..e1ad03a796 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java @@ -53,6 +53,7 @@ import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.invoice.api.InvoicePayment; import org.killbill.billing.invoice.api.InvoicePaymentType; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata; import org.killbill.billing.invoice.model.CreditAdjInvoiceItem; import org.killbill.billing.invoice.model.CreditBalanceAdjInvoiceItem; import org.killbill.billing.invoice.model.DefaultInvoice; @@ -556,7 +557,7 @@ public void testAccountBalanceWithNoPayments() throws EntityPersistenceException final UUID accountId = account.getId(); final UUID bundleId = UUID.randomUUID(); final LocalDate targetDate1 = new LocalDate(2011, 10, 6); - final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD); + final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD); invoiceUtil.createInvoice(invoice1, true, context); final LocalDate startDate = new LocalDate(2011, 3, 1); @@ -1109,7 +1110,8 @@ public void testInvoiceGenerationForImmediateChanges() throws InvoiceApiExceptio final BillingEventSet events = new MockBillingEventSet(); events.add(event1); - final Invoice invoice1 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, context); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, context); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); assertEquals(invoice1.getBalance(), KillBillMoney.of(TEN, invoice1.getCurrency())); invoiceList.add(invoice1); @@ -1127,7 +1129,9 @@ public void testInvoiceGenerationForImmediateChanges() throws InvoiceApiExceptio // second invoice should be for one half (14/28 days) the difference between the rate plans // this is a temporary state, since it actually contains an adjusting item that properly belong to invoice 1 - final Invoice invoice2 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, context); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, context); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); + assertEquals(invoice2.getBalance(), KillBillMoney.of(FIVE, invoice2.getCurrency())); invoiceList.add(invoice2); @@ -1159,7 +1163,8 @@ public void testInvoiceForFreeTrial() throws InvoiceApiException, CatalogApiExce events.add(event); final LocalDate targetDate = invoiceUtil.buildDate(2011, 1, 15); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, context); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, context); + final Invoice invoice = invoiceWithMetadata.getInvoice(); // expect one pro-ration item and one full-period item assertEquals(invoice.getNumberOfItems(), 2); @@ -1202,7 +1207,8 @@ public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiExce events.add(event1); final UUID accountId = account.getId(); - final Invoice invoice1 = generator.generateInvoice(account, events, null, new LocalDate(effectiveDate1), Currency.USD, context); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, new LocalDate(effectiveDate1), Currency.USD, context); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); assertNotNull(invoice1); assertEquals(invoice1.getNumberOfItems(), 1); assertEquals(invoice1.getBalance().compareTo(ZERO), 0); @@ -1218,7 +1224,8 @@ public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiExce "testEvent2", 2L, SubscriptionBaseTransitionType.PHASE); events.add(event2); - final Invoice invoice2 = generator.generateInvoice(account, events, invoiceList, new LocalDate(effectiveDate2), Currency.USD, context); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, new LocalDate(effectiveDate2), Currency.USD, context); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); assertNotNull(invoice2); assertEquals(invoice2.getNumberOfItems(), 1); assertEquals(invoice2.getBalance().compareTo(cheapAmount), 0); @@ -1228,7 +1235,8 @@ public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiExce //invoiceUtil.createInvoice(invoice2, invoice2.getTargetDate().getDayOfMonth(), callcontext); final DateTime effectiveDate3 = effectiveDate2.plusMonths(1); - final Invoice invoice3 = generator.generateInvoice(account, events, invoiceList, new LocalDate(effectiveDate3), Currency.USD, context); + final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, events, invoiceList, new LocalDate(effectiveDate3), Currency.USD, context); + final Invoice invoice3 = invoiceWithMetadata3.getInvoice(); assertNotNull(invoice3); assertEquals(invoice3.getNumberOfItems(), 1); assertEquals(invoice3.getBalance().compareTo(cheapAmount), 0); @@ -1239,7 +1247,8 @@ public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiExce @Test(groups = "slow") public void testInvoiceForEmptyEventSet() throws InvoiceApiException { final BillingEventSet events = new MockBillingEventSet(); - final Invoice invoice = generator.generateInvoice(account, events, null, new LocalDate(), Currency.USD, context); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, new LocalDate(), Currency.USD, context); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNull(invoice); } @@ -1273,7 +1282,8 @@ public void testMixedModeInvoicePersistence() throws InvoiceApiException, Catalo "testEvent2", 2L, SubscriptionBaseTransitionType.CHANGE); events.add(event2); - final Invoice invoice = generator.generateInvoice(account, events, null, new LocalDate(effectiveDate2), Currency.USD, context); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, new LocalDate(effectiveDate2), Currency.USD, context); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), 2); assertEquals(invoice.getBalance().compareTo(cheapAmount), 0); @@ -1344,7 +1354,8 @@ public void testRefundedInvoiceWithInvoiceItemAdjustmentWithRepair() throws Invo BillingPeriod.MONTHLY, 31, BillingMode.IN_ADVANCE, "new-event", 1L, SubscriptionBaseTransitionType.CREATE); events.add(event1); - final Invoice newInvoice = generator.generateInvoice(account, events, invoices, targetDate, Currency.USD, context); + final InvoiceWithMetadata newInvoiceWithMetadata = generator.generateInvoice(account, events, invoices, targetDate, Currency.USD, context); + final Invoice newInvoice = newInvoiceWithMetadata.getInvoice(); invoiceUtil.createInvoice(newInvoice, true, context); // VERIFY THAT WE STILL HAVE ONLY 2 ITEMS, MEANING THERE WERE NO REPAIR AND NO CBA GENERATED @@ -1379,7 +1390,8 @@ public void testInvoiceNumber() throws InvoiceApiException { "testEvent1", 1L, SubscriptionBaseTransitionType.CHANGE); events.add(event1); - Invoice invoice1 = generator.generateInvoice(account, events, invoices, new LocalDate(targetDate1), Currency.USD, context); + InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, invoices, new LocalDate(targetDate1), Currency.USD, context); + Invoice invoice1 = invoiceWithMetadata1.getInvoice(); invoices.add(invoice1); invoiceUtil.createInvoice(invoice1, true, context); invoice1 = new DefaultInvoice(invoiceDao.getById(invoice1.getId(), context)); @@ -1390,7 +1402,8 @@ public void testInvoiceNumber() throws InvoiceApiException { BillingPeriod.MONTHLY, 31, BillingMode.IN_ADVANCE, "testEvent2", 2L, SubscriptionBaseTransitionType.CHANGE); events.add(event2); - Invoice invoice2 = generator.generateInvoice(account, events, invoices, new LocalDate(targetDate2), Currency.USD, context); + InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, new LocalDate(targetDate2), Currency.USD, context); + Invoice invoice2 = invoiceWithMetadata2.getInvoice(); invoiceUtil.createInvoice(invoice2, true, context); invoice2 = new DefaultInvoice(invoiceDao.getById(invoice2.getId(), context)); assertNotNull(invoice2.getInvoiceNumber()); diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java index 9094ddcb4d..cadda9db3f 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java @@ -146,15 +146,15 @@ public int getMaxRawUsagePreviousPeriod() { @Test(groups = "fast") public void testWithNullEventSetAndNullInvoiceSet() throws InvoiceApiException { - final Invoice invoice = generator.generateInvoice(account, null, null, clock.getUTCToday(), Currency.USD, internalCallContext); - assertNull(invoice); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, null, null, clock.getUTCToday(), Currency.USD, internalCallContext); + assertNull(invoiceWithMetadata.getInvoice()); } @Test(groups = "fast") public void testWithEmptyEventSet() throws InvoiceApiException { final BillingEventSet events = new MockBillingEventSet(); - final Invoice invoice = generator.generateInvoice(account, events, null, clock.getUTCToday(), Currency.USD, internalCallContext); - assertNull(invoice); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, clock.getUTCToday(), Currency.USD, internalCallContext); + assertNull(invoiceWithMetadata.getInvoice()); } @Test(groups = "fast") @@ -172,8 +172,8 @@ public void testWithSingleMonthlyEvent() throws InvoiceApiException, CatalogApiE events.add(event); final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), 2); assertEquals(invoice.getBalance(), KillBillMoney.of(TWENTY, invoice.getCurrency())); @@ -209,8 +209,8 @@ public void testSimpleWithTimeZone() throws InvoiceApiException, CatalogApiExcep // Target date is the next BCD, in local time final LocalDate targetDate = invoiceUtil.buildDate(2012, 8, bcdLocal); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), 2); assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), invoiceUtil.buildDate(2012, 7, 16)); @@ -233,8 +233,8 @@ public void testSimpleWithSingleDiscountEvent() throws Exception { // Set a target date of today (start date) final LocalDate targetDate = startDate; - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), 1); assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), invoiceUtil.buildDate(2012, 7, 16)); @@ -255,8 +255,8 @@ public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiEx events.add(event); final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), 2); @@ -287,8 +287,8 @@ public void testTwoMonthlySubscriptionsWithAlignedBillingDates() throws InvoiceA events.add(event2); final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), 2); assertEquals(invoice.getBalance(), KillBillMoney.of(rate1.add(rate2), invoice.getCurrency())); @@ -313,8 +313,8 @@ public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() throws InvoiceApiExce final LocalDate targetDate = invoiceUtil.buildDate(2011, 12, 3); final UUID accountId = UUID.randomUUID(); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), 4); @@ -354,8 +354,8 @@ public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() throws InvoiceApiExceptio events.add(event3); final LocalDate targetDate = invoiceUtil.buildDate(2011, 12, 3); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), 4); assertEquals(invoice.getBalance(), KillBillMoney.of(rate1.add(rate2).add(TWO.multiply(rate3)), invoice.getCurrency())); @@ -376,13 +376,14 @@ public void testSingleEventWithExistingInvoice() throws InvoiceApiException, Cat events.add(event1); LocalDate targetDate = invoiceUtil.buildDate(2011, 12, 1); - final Invoice invoice1 = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); final List existingInvoices = new ArrayList(); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); existingInvoices.add(invoice1); targetDate = invoiceUtil.buildDate(2011, 12, 3); - final Invoice invoice2 = generator.generateInvoice(account, events, existingInvoices, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, existingInvoices, targetDate, Currency.USD, internalCallContext); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); assertNull(invoice2); } @@ -554,8 +555,8 @@ public void testZeroDollarEvents() throws InvoiceApiException, CatalogApiExcepti final LocalDate targetDate = invoiceUtil.buildDate(2011, 1, 1); events.add(createBillingEvent(UUID.randomUUID(), UUID.randomUUID(), targetDate, plan, planPhase, 1)); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertEquals(invoice.getNumberOfItems(), 1); } @@ -569,9 +570,11 @@ public void testEndDateIsCorrect() throws InvoiceApiException, CatalogApiExcepti events.add(createBillingEvent(UUID.randomUUID(), UUID.randomUUID(), startDate, plan, planPhase, startDate.getDayOfMonth())); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); final RecurringInvoiceItem item = (RecurringInvoiceItem) invoice.getInvoiceItems().get(0); + // end date of the invoice item should be equal to exactly one month later (rounded) assertEquals(item.getEndDate(), startDate.plusMonths(1)); } @@ -605,13 +608,15 @@ public void testFixedPriceLifeCycle() throws InvoiceApiException { events.add(event2); events.add(event1); - final Invoice invoice1 = generator.generateInvoice(account, events, null, new LocalDate("2012-02-01"), Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, new LocalDate("2012-02-01"), Currency.USD, internalCallContext); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); assertNotNull(invoice1); assertEquals(invoice1.getNumberOfItems(), 1); final List invoiceList = new ArrayList(); invoiceList.add(invoice1); - final Invoice invoice2 = generator.generateInvoice(account, events, invoiceList, new LocalDate("2012-04-05"), Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, new LocalDate("2012-04-05"), Currency.USD, internalCallContext); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); assertNotNull(invoice2); assertEquals(invoice2.getNumberOfItems(), 1); final FixedPriceInvoiceItem item = (FixedPriceInvoiceItem) invoice2.getInvoiceItems().get(0); @@ -635,7 +640,8 @@ public void testMixedModeLifeCycle() throws InvoiceApiException, CatalogApiExcep events.add(event1); // ensure both components are invoiced - final Invoice invoice1 = generator.generateInvoice(account, events, null, startDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, startDate, Currency.USD, internalCallContext); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); assertNotNull(invoice1); assertEquals(invoice1.getNumberOfItems(), 2); assertEquals(invoice1.getBalance(), KillBillMoney.of(FIFTEEN, invoice1.getCurrency())); @@ -647,7 +653,8 @@ public void testMixedModeLifeCycle() throws InvoiceApiException, CatalogApiExcep final LocalDate currentDate = startDate.plusMonths(1); // ensure that only the recurring price is invoiced - final Invoice invoice2 = generator.generateInvoice(account, events, invoiceList, currentDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, currentDate, Currency.USD, internalCallContext); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); assertNotNull(invoice2); assertEquals(invoice2.getNumberOfItems(), 1); assertEquals(invoice2.getBalance(), KillBillMoney.of(FIVE, invoice2.getCurrency())); @@ -672,8 +679,8 @@ public void testFixedModePlanChange() throws InvoiceApiException, CatalogApiExce events.add(event1); // ensure that a single invoice item is generated for the fixed cost - final Invoice invoice1 = generator.generateInvoice(account, events, null, startDate, Currency.USD, internalCallContext); - assertNotNull(invoice1); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, startDate, Currency.USD, internalCallContext); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice();assertNotNull(invoice1); assertEquals(invoice1.getNumberOfItems(), 1); assertEquals(invoice1.getBalance(), KillBillMoney.of(fixedCost1, invoice1.getCurrency())); @@ -686,8 +693,8 @@ public void testFixedModePlanChange() throws InvoiceApiException, CatalogApiExce events.add(event2); // ensure that a single invoice item is generated for the fixed cost - final Invoice invoice2 = generator.generateInvoice(account, events, invoiceList, phaseChangeDate, Currency.USD, internalCallContext); - assertNotNull(invoice2); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, phaseChangeDate, Currency.USD, internalCallContext); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); assertEquals(invoice2.getNumberOfItems(), 1); assertEquals(invoice2.getBalance(), KillBillMoney.of(fixedCost2, invoice2.getCurrency())); } @@ -718,7 +725,8 @@ public void testInvoiceGenerationFailureScenario() throws InvoiceApiException, C final LocalDate discountPhaseEndDate = trialPhaseEndDate.plusMonths(6); events.add(createBillingEvent(subscriptionId, bundleId, discountPhaseEndDate, plan1, phase3, BILL_CYCLE_DAY)); - final Invoice invoice1 = generator.generateInvoice(account, events, null, creationDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, creationDate, Currency.USD, internalCallContext); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); assertNotNull(invoice1); assertEquals(invoice1.getNumberOfItems(), 1); assertEquals(invoice1.getBalance().compareTo(ZERO), 0); @@ -726,7 +734,8 @@ public void testInvoiceGenerationFailureScenario() throws InvoiceApiException, C final List invoiceList = new ArrayList(); invoiceList.add(invoice1); - final Invoice invoice2 = generator.generateInvoice(account, events, invoiceList, trialPhaseEndDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, trialPhaseEndDate, Currency.USD, internalCallContext); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); assertNotNull(invoice2); assertEquals(invoice2.getNumberOfItems(), 1); assertEquals(invoice2.getInvoiceItems().get(0).getStartDate(), trialPhaseEndDate); @@ -734,7 +743,8 @@ public void testInvoiceGenerationFailureScenario() throws InvoiceApiException, C invoiceList.add(invoice2); LocalDate targetDate = new LocalDate(trialPhaseEndDate.getYear(), trialPhaseEndDate.getMonthOfYear(), BILL_CYCLE_DAY); - final Invoice invoice3 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, internalCallContext); + final Invoice invoice3 = invoiceWithMetadata3.getInvoice(); assertNotNull(invoice3); assertEquals(invoice3.getNumberOfItems(), 1); assertEquals(invoice3.getInvoiceItems().get(0).getStartDate(), targetDate); @@ -742,7 +752,8 @@ public void testInvoiceGenerationFailureScenario() throws InvoiceApiException, C invoiceList.add(invoice3); targetDate = targetDate.plusMonths(6); - final Invoice invoice4 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata4 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, internalCallContext); + final Invoice invoice4 = invoiceWithMetadata4.getInvoice(); assertNotNull(invoice4); assertEquals(invoice4.getNumberOfItems(), 7); } @@ -801,7 +812,8 @@ private void testInvoiceGeneration(final UUID accountId, final BillingEventSet e final LocalDate targetDate, final int expectedNumberOfItems, final BigDecimal expectedAmount) throws InvoiceApiException { final Currency currency = Currency.USD; - final Invoice invoice = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertNotNull(invoice); assertEquals(invoice.getNumberOfItems(), expectedNumberOfItems); existingInvoices.add(invoice); @@ -827,7 +839,8 @@ public void testWithFullRepairInvoiceGeneration() throws CatalogApiException, In events.add(createBillingEvent(baseSubscription.getId(), baseSubscription.getBundleId(), april25, basePlan, basePlanEvergreen, 25)); // generate invoice - final Invoice invoice1 = generator.generateInvoice(account, events, null, april25, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, april25, Currency.USD, internalCallContext); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); assertNotNull(invoice1); assertEquals(invoice1.getNumberOfItems(), 1); assertEquals(invoice1.getBalance().compareTo(TEN), 0); @@ -848,7 +861,9 @@ public void testWithFullRepairInvoiceGeneration() throws CatalogApiException, In events.add(createBillingEvent(addOnSubscription2.getId(), baseSubscription.getBundleId(), april28, addOn2Plan, addOn2PlanPhaseEvergreen, 25)); // generate invoice - final Invoice invoice2 = generator.generateInvoice(account, events, invoices, april28, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, april28, Currency.USD, internalCallContext); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); + invoices.add(invoice2); assertNotNull(invoice2); assertEquals(invoice2.getNumberOfItems(), 2); @@ -865,7 +880,8 @@ public void testWithFullRepairInvoiceGeneration() throws CatalogApiException, In // generate invoice final LocalDate may1 = new LocalDate(2012, 5, 1); - final Invoice invoice3 = generator.generateInvoice(account, newEvents, invoices, may1, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, newEvents, invoices, may1, Currency.USD, internalCallContext); + final Invoice invoice3 = invoiceWithMetadata3.getInvoice(); assertNotNull(invoice3); assertEquals(invoice3.getNumberOfItems(), 3); // -4.50 -18 - 10 (to correct the previous 2 invoices) + 4.50 + 13 @@ -887,7 +903,8 @@ public void testRepairForPaidInvoice() throws CatalogApiException, InvoiceApiExc final BillingEventSet events = new MockBillingEventSet(); events.add(createBillingEvent(originalSubscription.getId(), originalSubscription.getBundleId(), april25, originalPlan, originalPlanEvergreen, 25)); - final Invoice invoice1 = generator.generateInvoice(account, events, null, april25, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, april25, Currency.USD, internalCallContext); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); printDetailInvoice(invoice1); @@ -909,7 +926,8 @@ public void testRepairForPaidInvoice() throws CatalogApiException, InvoiceApiExc events.add(createBillingEvent(newSubscription.getId(), originalSubscription.getBundleId(), april25, newPlan, newPlanEvergreen, 25)); // generate a new invoice - final Invoice invoice2 = generator.generateInvoice(account, events, invoices, april25, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, april25, Currency.USD, internalCallContext); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); printDetailInvoice(invoice2); assertEquals(invoice2.getNumberOfItems(), 2); @@ -973,7 +991,8 @@ public void testRegressionFor170() throws EntityPersistenceException, InvoiceApi // Generate a new invoice - final Invoice invoice = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext); + final Invoice invoice = invoiceWithMetadata.getInvoice(); assertEquals(invoice.getNumberOfItems(), 7); assertEquals(invoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING); assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), new LocalDate(2013, 6, 15)); @@ -1007,7 +1026,8 @@ public void testRegressionFor170() throws EntityPersistenceException, InvoiceApi existingInvoices.add(invoice); // Generate next invoice (no-op) - final Invoice newInvoice = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext); + final InvoiceWithMetadata newInvoiceWithMetdata = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext); + final Invoice newInvoice = newInvoiceWithMetdata.getInvoice(); assertNull(newInvoice); } @@ -1054,9 +1074,9 @@ public void testAutoInvoiceOffAccount() throws Exception { final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3); final UUID accountId = UUID.randomUUID(); - final Invoice invoice = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); - assertNull(invoice); + assertNull(invoiceWithMetadata.getInvoice()); } public void testAutoInvoiceOffWithCredits() throws CatalogApiException, InvoiceApiException { @@ -1082,7 +1102,8 @@ public void testAutoInvoiceOffWithCredits() throws CatalogApiException, InvoiceA eventSet.add(createBillingEvent(subscriptionId2, bundleId, startDate, plan2, plan2phase1, 1)); // generate the first invoice - final Invoice invoice1 = generator.generateInvoice(account, eventSet, invoices, startDate, currency, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, eventSet, invoices, startDate, currency, internalCallContext); + final Invoice invoice1 = invoiceWithMetadata1.getInvoice(); assertNotNull(invoice1); assertTrue(invoice1.getBalance().compareTo(FIFTEEN.add(TWELVE)) == 0); invoices.add(invoice1); @@ -1093,7 +1114,8 @@ public void testAutoInvoiceOffWithCredits() throws CatalogApiException, InvoiceA eventSet.addSubscriptionWithAutoInvoiceOff(subscriptionId1); final LocalDate targetDate2 = startDate.plusMonths(1); - final Invoice invoice2 = generator.generateInvoice(account, eventSet, invoices, targetDate2, currency, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, eventSet, invoices, targetDate2, currency, internalCallContext); + final Invoice invoice2 = invoiceWithMetadata2.getInvoice(); assertNotNull(invoice2); assertTrue(invoice2.getBalance().compareTo(TWELVE) == 0); invoices.add(invoice2); @@ -1101,7 +1123,8 @@ public void testAutoInvoiceOffWithCredits() throws CatalogApiException, InvoiceA final LocalDate targetDate3 = targetDate2.plusMonths(1); eventSet.clearSubscriptionsWithAutoInvoiceOff(); eventSet.add(subscription1creation); - final Invoice invoice3 = generator.generateInvoice(account, eventSet, invoices, targetDate3, currency, internalCallContext); + final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, eventSet, invoices, targetDate3, currency, internalCallContext); + final Invoice invoice3 = invoiceWithMetadata3.getInvoice(); assertNotNull(invoice3); assertTrue(invoice3.getBalance().compareTo(FIFTEEN.multiply(TWO).add(TWELVE)) == 0); } From 5af598e2603281aed053fb26c448b7d5fd793ed3 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 1 Sep 2015 20:22:21 -0700 Subject: [PATCH 048/137] invoice: Refactor the computation of next invoice future notitications (pass 2) Add new method getNextBillingCycleDate which is used to compute next notification for IN_ARREAR recurring mode. Add BillingIntervalDetails tests for IN_ARREAR recurring mode --- .../billing/invoice/InvoiceDispatcher.java | 24 +- .../generator/BillingIntervalDetail.java | 15 +- .../generator/DefaultInvoiceGenerator.java | 100 ++++--- .../generator/InvoiceWithMetadata.java | 131 +++++++-- .../invoice/model/BillingModeGenerator.java | 8 +- .../model/DefaultBillingModeGenerator.java | 8 +- ...voiceItemDataWithNextBillingCycleDate.java | 45 ++++ ...> TestInAdvanceBillingIntervalDetail.java} | 2 +- .../TestInArrearBillingIntervalDetail.java | 248 ++++++++++++++++++ .../model/TestInAdvanceBillingMode.java | 4 +- .../model/TestInArrearBillingMode.java | 3 +- .../invoice/proRations/ProRationTestBase.java | 9 +- 12 files changed, 508 insertions(+), 89 deletions(-) create mode 100644 invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java rename invoice/src/test/java/org/killbill/billing/invoice/generator/{TestBillingIntervalDetail.java => TestInAdvanceBillingIntervalDetail.java} (99%) create mode 100644 invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java index 6f4b247304..58194a67cc 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java @@ -70,6 +70,7 @@ import org.killbill.billing.invoice.generator.InvoiceGenerator; import org.killbill.billing.invoice.generator.InvoiceWithMetadata; import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates.UsageDef; import org.killbill.billing.invoice.model.DefaultInvoice; import org.killbill.billing.invoice.model.FixedPriceInvoiceItem; import org.killbill.billing.invoice.model.InvoiceItemFactory; @@ -101,7 +102,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; @@ -118,7 +118,7 @@ public class InvoiceDispatcher { private static final int NB_LOCK_TRY = 5; private static final Ordering UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural(); - + private final static Joiner JOINER_COMMA = Joiner.on(","); private static final NullDryRunArguments NULL_DRY_RUN_ARGUMENTS = new NullDryRunArguments(); private final InvoiceGenerator generator; @@ -358,9 +358,9 @@ private FutureAccountNotifications createNextFutureNotificationDate(final Invoic } // Add next usage dates if any if (subscriptionFutureNotificationDates.getNextUsageDates() != null) { - for (String usageName : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) { - final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageName); - final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageName, nextNotificationDateForUsage, dateAndTimeZoneContext, billingEvents); + for (UsageDef usageDef : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) { + final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageDef); + final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageDef.getUsageName(), nextNotificationDateForUsage, dateAndTimeZoneContext, billingEvents); perSubscriptionNotifications.add(new SubscriptionNotification(subscriptionUsageCallbackDate, true)); } } @@ -409,14 +409,18 @@ public UUID apply(@Nullable final InvoiceItem input) { } private void logInvoiceWithItems(final Account account, final Invoice invoice, final LocalDate targetDate, final Set adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithItems) { + final StringBuilder tmp = new StringBuilder(); if (isRealInvoiceWithItems) { - log.info("Generated invoice {} with {} items for accountId {} and targetDate {}", new Object[]{invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate}); + tmp.append(String.format("Generated invoice %s with %d items for accountId %s and targetDate %s:\n", invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate)); } else { - final Joiner joiner = Joiner.on(","); - final String adjustedInvoices = joiner.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()])); - log.info("Adjusting existing invoices {} with {} items for accountId {} and targetDate {})", new Object[]{adjustedInvoices, invoice.getNumberOfItems(), - account.getId(), targetDate}); + final String adjustedInvoices = JOINER_COMMA.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()])); + tmp.append(String.format("Adjusting existing invoices %s with %d items for accountId %s and targetDate %s:\n", + adjustedInvoices, invoice.getNumberOfItems(), account.getId(), targetDate)); + } + for (InvoiceItem item : invoice.getInvoiceItems()) { + tmp.append(String.format("\t item = %s\n", item)); } + log.info(tmp.toString()); } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java index 513cd870ef..51f284589f 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java @@ -30,7 +30,9 @@ public class BillingIntervalDetail { private final int billingCycleDay; private final BillingPeriod billingPeriod; private final BillingMode billingMode; + // First date after the startDate aligned with the BCD private LocalDate firstBillingCycleDate; + // Date up to which we should bill private LocalDate effectiveEndDate; private LocalDate lastBillingCycleDate; @@ -67,6 +69,14 @@ public LocalDate getLastBillingCycleDate() { return lastBillingCycleDate; } + public LocalDate getNextBillingCycleDate() { + final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths(); + final LocalDate proposedDate = lastBillingCycleDate != null ? lastBillingCycleDate.plusMonths(numberOfMonthsInPeriod) : firstBillingCycleDate; + final LocalDate nextBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay); + return nextBillingCycleDate; + } + + public boolean hasSomethingToBill() { return effectiveEndDate != null /* IN_ARREAR mode prior we have reached our firstBillingCycleDate */ && (endDate == null || endDate.isAfter(startDate)); /* When there is an endDate, it should be > startDate since we don't bill for less than a day */ @@ -171,8 +181,9 @@ private void calculateInAdvanceEffectiveEndDate() { private void calculateLastBillingCycleDate() { - if (effectiveEndDate == null) { - lastBillingCycleDate = firstBillingCycleDate; + // IN_ARREAR cases + if (effectiveEndDate == null || effectiveEndDate.compareTo(firstBillingCycleDate) < 0 ) { + lastBillingCycleDate = null; return; } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index 070348df23..3d8fecc5a1 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -52,6 +52,7 @@ import org.killbill.billing.invoice.model.InvalidDateSequenceException; import org.killbill.billing.invoice.model.RecurringInvoiceItem; import org.killbill.billing.invoice.model.RecurringInvoiceItemData; +import org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate; import org.killbill.billing.invoice.tree.AccountItemTree; import org.killbill.billing.invoice.usage.RawUsageOptimizer; import org.killbill.billing.invoice.usage.RawUsageOptimizer.RawUsageOptimizerResult; @@ -65,6 +66,8 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -123,8 +126,6 @@ private List generateUsageConsumableInArrearItems(final Account acc final Map perSubscriptionFutureNotificationDates, final InternalCallContext internalCallContext) throws InvoiceApiException { - final Map perSubscriptionUsage = new HashMap(); - final Map> perSubscriptionConsumableInArrearUsageItems = extractPerSubscriptionExistingConsumableInArrearUsageItems(eventSet.getUsages(), existingInvoices); try { final List items = Lists.newArrayList(); @@ -163,10 +164,9 @@ public boolean apply(@Nullable final Usage input) { final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); - final List newProposedItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); - items.addAll(newProposedItems); - updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newProposedItems, perSubscriptionFutureNotificationDates); - + final List newInArrearUsageItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + items.addAll(newInArrearUsageItems); + updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newInArrearUsageItems, BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); curEvents = Lists.newArrayList(); } curSubscriptionId = subscriptionId; @@ -176,9 +176,9 @@ public boolean apply(@Nullable final Usage input) { final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); - final List newProposedItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); - items.addAll(newProposedItems); - updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newProposedItems, perSubscriptionFutureNotificationDates); + final List newInArrearUsageItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + items.addAll(newInArrearUsageItems); + updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newInArrearUsageItems, BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); } return items; @@ -188,14 +188,14 @@ public boolean apply(@Nullable final Usage input) { } - private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final List newProposedItems, Map perSubscriptionFutureNotificationDates) { - for (final InvoiceItem item : newProposedItems) { + private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final List newInArrearUsageItems, final BillingMode usageBillingMode, final Map perSubscriptionFutureNotificationDates) { + for (final InvoiceItem item : newInArrearUsageItems) { SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); if (subscriptionFutureNotificationDates == null) { - subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(); + subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(null); perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); } - subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(item.getUsageName(), item.getEndDate()); + subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(item.getUsageName(), usageBillingMode, item.getEndDate()); } } @@ -285,7 +285,7 @@ private LocalDate adjustTargetDate(final List existingInvoices, final L private List processRecurringBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List proposedItems, - Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { + final Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { if (events.size() == 0) { return proposedItems; @@ -305,37 +305,18 @@ private List processRecurringBillingEvents(final UUID invoiceId, fi if (!events.getSubscriptionIdsWithAutoInvoiceOff(). contains(thisEvent.getSubscription().getId())) { // don't consider events for subscriptions that have auto_invoice_off final BillingEvent adjustedNextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null; - final List newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode()); + final List newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate); proposedItems.addAll(newProposedItems); - updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), newProposedItems, perSubscriptionFutureNotificationDate); } } - final List newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode()); + final List newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate); proposedItems.addAll(newProposedItems); - updatePerSubscriptionNextNotificationDate(nextEvent.getSubscription().getId(), newProposedItems, perSubscriptionFutureNotificationDate); log.info(logStringBuilder.toString()); return proposedItems; } - private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final List newProposedItems, Map perSubscriptionFutureNotificationDates) { - for (final InvoiceItem item : newProposedItems) { - if ((item.getEndDate() != null) && - (item.getAmount() == null || - item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) { - SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); - if (subscriptionFutureNotificationDates == null) { - subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(); - perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); - } - subscriptionFutureNotificationDates.updateNextRecurringDateIfRequired(item.getEndDate()); - } - } - } - - - private List processFixedBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List proposedItems) { final Iterator eventIt = events.iterator(); @@ -353,7 +334,8 @@ private List processFixedBillingEvents(final UUID invoiceId, final // Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day) private List processRecurringEvent(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, final LocalDate targetDate, final Currency currency, - final StringBuilder logStringBuilder, final BillingMode billingMode) throws InvoiceApiException { + final StringBuilder logStringBuilder, final BillingMode billingMode, + final Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { final List items = new ArrayList(); // Handle recurring items @@ -366,14 +348,14 @@ private List processRecurringEvent(final UUID invoiceId, final UUID final int billCycleDayLocal = thisEvent.getBillCycleDayLocal(); - final List itemData; + final RecurringInvoiceItemDataWithNextBillingCycleDate itemDataWithNextBillingCycleDate; try { - itemData = billingModeGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode); + itemDataWithNextBillingCycleDate = billingModeGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode); } catch (InvalidDateSequenceException e) { throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate); } - for (final RecurringInvoiceItemData itemDatum : itemData) { + for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) { final BigDecimal rate = thisEvent.getRecurringPrice(); if (rate != null) { @@ -390,6 +372,8 @@ private List processRecurringEvent(final UUID invoiceId, final UUID items.add(recurringItem); } } + updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, thisEvent.getBillingMode(), perSubscriptionFutureNotificationDate); + } } @@ -400,11 +384,45 @@ private List processRecurringEvent(final UUID invoiceId, final UUID logStringBuilder.append("\n\t") .append(item); } - return items; } - InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, + private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List newProposedItems, final BillingMode billingMode, final Map perSubscriptionFutureNotificationDates) { + + LocalDate nextNotificationDate = null; + switch(billingMode) { + case IN_ADVANCE: + for (final InvoiceItem item : newProposedItems) { + if ((item.getEndDate() != null) && + (item.getAmount() == null || + item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) { + if (nextNotificationDate == null) { + nextNotificationDate = item.getEndDate(); + } else { + nextNotificationDate = nextNotificationDate.compareTo(item.getEndDate()) > 0 ? nextNotificationDate : item.getEndDate(); + } + } + } + break; + case IN_ARREAR: + nextNotificationDate = nextBillingCycleDate; + break; + default: + throw new IllegalStateException("Unrecognized billing mode " + billingMode); + } + if (nextNotificationDate != null) { + SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); + if (subscriptionFutureNotificationDates == null) { + subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(billingMode); + perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); + } + subscriptionFutureNotificationDates.updateNextRecurringDateIfRequired(nextNotificationDate); + + } + } + + + private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, final LocalDate targetDate, final Currency currency) { final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java index a3192453e3..0e0f30e9e4 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java @@ -18,25 +18,80 @@ package org.killbill.billing.invoice.generator; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.UUID; import javax.annotation.Nullable; import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.invoice.api.Invoice; +import org.killbill.billing.invoice.api.InvoiceItem; +import org.killbill.billing.invoice.api.InvoiceItemType; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates.UsageDef; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; public class InvoiceWithMetadata { private final Invoice invoice; private final Map perSubscriptionFutureNotificationDates; + + public InvoiceWithMetadata(final Invoice invoice, final Map perSubscriptionFutureNotificationDates) { + this.invoice = invoice; + this.perSubscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates; + build(); + } + + public Invoice getInvoice() { + return invoice; + } + + public Map getPerSubscriptionFutureNotificationDates() { + return perSubscriptionFutureNotificationDates; + } + + // Remove all the IN_ADVANCE items for which we have no invoice items + private void build() { + for (final UUID subscriptionId : perSubscriptionFutureNotificationDates.keySet()) { + final SubscriptionFutureNotificationDates tmp = perSubscriptionFutureNotificationDates.get(subscriptionId); + if (tmp.getRecurringBillingMode() == BillingMode.IN_ADVANCE && !hasItemsForSubscription(subscriptionId, InvoiceItemType.RECURRING)) { + tmp.resetNextRecurringDate(); + } + if (tmp.getNextUsageDates() != null) { + final Iterator it = tmp.getNextUsageDates().keySet().iterator(); + while (it.hasNext()) { + final UsageDef usageDef = it.next(); + if (usageDef.getBillingMode() == BillingMode.IN_ADVANCE && !hasItemsForSubscription(subscriptionId, InvoiceItemType.USAGE)) { + it.remove(); + } + } + } + } + } + + private boolean hasItemsForSubscription(final UUID subscriptionId, final InvoiceItemType invoiceItemType) { + return invoice != null && Iterables.any(invoice.getInvoiceItems(), new Predicate() { + @Override + public boolean apply(final InvoiceItem input) { + return input.getInvoiceItemType() == invoiceItemType && + input.getSubscriptionId().equals(subscriptionId); + } + }); + } + public static class SubscriptionFutureNotificationDates { + private final BillingMode recurringBillingMode; + private LocalDate nextRecurringDate; - private Map nextUsageDates; + private Map nextUsageDates; - public SubscriptionFutureNotificationDates() { + public SubscriptionFutureNotificationDates(final BillingMode recurringBillingMode) { + this.recurringBillingMode = recurringBillingMode; this.nextRecurringDate = null; this.nextUsageDates = null; } @@ -45,22 +100,32 @@ public void updateNextRecurringDateIfRequired(final LocalDate nextRecurringDateC nextRecurringDate = getMaxDate(nextRecurringDate, nextRecurringDateCandidate); } - public void updateNextUsageDateIfRequired(final String usageName, final LocalDate nextUsageDateCandidate) { + public void updateNextUsageDateIfRequired(final String usageName, final BillingMode billingMode, final LocalDate nextUsageDateCandidate) { if (nextUsageDates == null) { - nextUsageDates = new HashMap(); + nextUsageDates = new HashMap(); } - final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageName), nextUsageDateCandidate); - nextUsageDates.put(usageName, nextUsageDate); + final UsageDef usageDef = new UsageDef(usageName, billingMode); + final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageDef), nextUsageDateCandidate); + nextUsageDates.put(usageDef, nextUsageDate); } public LocalDate getNextRecurringDate() { return nextRecurringDate; } - public Map getNextUsageDates() { + public Map getNextUsageDates() { return nextUsageDates; } + public BillingMode getRecurringBillingMode() { + return recurringBillingMode; + } + + public void resetNextRecurringDate() { + nextRecurringDate = null; + } + + private static LocalDate getMaxDate(@Nullable final LocalDate existingDate, final LocalDate nextDateCandidate) { if (existingDate == null) { return nextDateCandidate; @@ -69,19 +134,49 @@ private static LocalDate getMaxDate(@Nullable final LocalDate existingDate, fina } } - } - public InvoiceWithMetadata(final Invoice invoice, final Map perSubscriptionFutureNotificationDates) { - this.invoice = invoice; - this.perSubscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates; - } + public static class UsageDef { - public Invoice getInvoice() { - return invoice; - } + private final String usageName; + private final BillingMode billingMode; - public Map getPerSubscriptionFutureNotificationDates() { - return perSubscriptionFutureNotificationDates; - } + public UsageDef(final String usageName, final BillingMode billingMode) { + this.usageName = usageName; + this.billingMode = billingMode; + } + + public String getUsageName() { + return usageName; + } + + public BillingMode getBillingMode() { + return billingMode; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof UsageDef)) { + return false; + } + + final UsageDef usageDef = (UsageDef) o; + if (usageName != null ? !usageName.equals(usageDef.usageName) : usageDef.usageName != null) { + return false; + } + return billingMode == usageDef.billingMode; + + } + + @Override + public int hashCode() { + int result = usageName != null ? usageName.hashCode() : 0; + result = 31 * result + (billingMode != null ? billingMode.hashCode() : 0); + return result; + } + } + } } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java index 26fbfd90b7..19a8496a6e 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java @@ -16,18 +16,14 @@ package org.killbill.billing.invoice.model; -import java.util.List; - import javax.annotation.Nullable; -import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; - import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; public interface BillingModeGenerator { - List generateInvoiceItemData(LocalDate startDate, @Nullable LocalDate endDate, LocalDate targetDate, - int billingCycleDay, BillingPeriod billingPeriod, BillingMode billingMode) throws InvalidDateSequenceException; + RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData(LocalDate startDate, @Nullable LocalDate endDate, LocalDate targetDate, + int billingCycleDay, BillingPeriod billingPeriod, BillingMode billingMode) throws InvalidDateSequenceException; } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java index 29bcc1f7f9..2da91696a5 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java @@ -38,7 +38,7 @@ public class DefaultBillingModeGenerator implements BillingModeGenerator { private static final Logger log = LoggerFactory.getLogger(DefaultBillingModeGenerator.class); @Override - public List generateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate, + public RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate, final LocalDate targetDate, final int billingCycleDayLocal, final BillingPeriod billingPeriod, @@ -56,7 +56,7 @@ public List generateInvoiceItemData(final LocalDate st // We are not billing for less than a day if (!billingIntervalDetail.hasSomethingToBill()) { - return results; + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode); } // // If there is an endDate and that endDate is before our first coming firstBillingCycleDate, all we have to do @@ -66,7 +66,7 @@ public List generateInvoiceItemData(final LocalDate st final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod); final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods); results.add(itemData); - return results; + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode); } // @@ -126,6 +126,6 @@ public List generateInvoiceItemData(final LocalDate st results.add(itemData); } } - return results; + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode); } } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java new file mode 100644 index 0000000000..3533240281 --- /dev/null +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.model; + +import java.util.List; + +import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; +import org.killbill.billing.invoice.generator.BillingIntervalDetail; + +public class RecurringInvoiceItemDataWithNextBillingCycleDate { + + private final List itemData; + private final BillingIntervalDetail billingIntervalDetail; + private final BillingMode billingMode; + + public RecurringInvoiceItemDataWithNextBillingCycleDate(final List itemData, final BillingIntervalDetail billingIntervalDetail,final BillingMode billingMode) { + this.itemData = itemData; + this.billingIntervalDetail = billingIntervalDetail; + this.billingMode = billingMode; + } + + public List getItemData() { + return itemData; + } + + public LocalDate getNextBillingCycleDate() { + return billingIntervalDetail.getNextBillingCycleDate(); + } +} diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestBillingIntervalDetail.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java similarity index 99% rename from invoice/src/test/java/org/killbill/billing/invoice/generator/TestBillingIntervalDetail.java rename to invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java index 8021c2cba2..2328f56657 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestBillingIntervalDetail.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java @@ -24,7 +24,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.InvoiceTestSuiteNoDB; -public class TestBillingIntervalDetail extends InvoiceTestSuiteNoDB { +public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB { /* * diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java new file mode 100644 index 0000000000..009ee8c021 --- /dev/null +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java @@ -0,0 +1,248 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.generator; + +import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; +import org.killbill.billing.catalog.api.BillingPeriod; +import org.killbill.billing.invoice.InvoiceTestSuiteNoDB; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TestInArrearBillingIntervalDetail extends InvoiceTestSuiteNoDB { + + + /* + * TD + * BCD Start + * |---------|----------------- + * + */ + @Test(groups = "fast") + public void testScenarioBCDBeforeStart1() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate targetDate = new LocalDate("2012-01-16"); + final int bcd = 13; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertFalse(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-13")); + Assert.assertNull(billingIntervalDetail.getEffectiveEndDate()); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-13")); + } + + /* + * + * BCD Start TD = (next BCD) + * |---------|----------|------- + * + */ + @Test(groups = "fast") + public void testScenarioBCDBeforeStart2() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate targetDate = new LocalDate("2012-02-13"); + final int bcd = 13; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-13")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-02-13")); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-03-13")); + } + + + /* + * BCD + * Start TD + * |---------|----------------- + * + */ + @Test(groups = "fast") + public void testScenarioBCDAEqualsStart1() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate targetDate = new LocalDate("2012-01-19"); + final int bcd = 16; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-16")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-16")); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-16")); + } + + + /* + * + * Start TD BCD + * |---------|----------|------- + * + */ + @Test(groups = "fast") + public void testScenarioBCDAfterStart1() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate targetDate = new LocalDate("2012-01-19"); + final int bcd = 25; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertFalse(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-25")); + Assert.assertNull(billingIntervalDetail.getEffectiveEndDate()); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-01-25")); + } + + + /* + * TD + * Start End BCD + * |---------|------------|------- + * + */ + @Test(groups = "fast") + public void testScenarioBCDAfterStart2() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate end = new LocalDate("2012-01-19"); + final LocalDate targetDate = new LocalDate("2012-01-25"); + final int bcd = 25; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-25")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), end); + // STEPH maybe we should change because we actually don't want a notification + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-01-25")); + } + + + /* + * TD + * Start BCD + * |--------------------|------- + * + */ + @Test(groups = "fast") + public void testScenarioBCDAfterStart3() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate targetDate = new LocalDate("2012-01-25"); + final int bcd = 25; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-25")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-25")); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-25")); + } + + + /* + * + * Start BCD end TD next BCD + * |-------|------|----|------|--- + * + */ + @Test(groups = "fast") + public void testScenarioEndDateBetweenPeriod1() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate end = new LocalDate("2012-01-20"); + final LocalDate targetDate = new LocalDate("2012-01-25"); + final int bcd = 18; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18")); + } + + /* + * + * Start BCD TD next BCD + * |-------|----------|------|--- + * + */ + @Test(groups = "fast") + public void testScenarioEndDateBetweenPeriod2() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate targetDate = new LocalDate("2012-01-25"); + final int bcd = 18; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18")); + } + + /* + * + * Start BCD TD end next BCD + * |-------|----------|----|------|--- + * + */ + @Test(groups = "fast") + public void testScenarioEndDateBetweenPeriod3() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate end = new LocalDate("2012-01-28"); + final LocalDate targetDate = new LocalDate("2012-01-25"); + final int bcd = 18; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18")); + } + + /* + * TD + * Start BCD next BCD + * |-------|--------------------|--- + * + */ + @Test(groups = "fast") + public void testScenarioTargetDateOnNextBCD1() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate targetDate = new LocalDate("2012-02-18"); + final int bcd = 18; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-02-18")); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-03-18")); + } + + /* + * TD + * Start BCD end next BCD + * |-------|-------------|-------|--- + * + */ + @Test(groups = "fast") + public void testScenarioTargetDateOnNextBCD2() throws Exception { + final LocalDate start = new LocalDate("2012-01-16"); + final LocalDate end = new LocalDate("2012-02-16"); + final LocalDate targetDate = new LocalDate("2012-02-18"); + final int bcd = 18; + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR); + + Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); + Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-02-16")); + Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18")); + } +} diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java index db121f60e1..5766409f5c 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java @@ -151,8 +151,8 @@ private void verifyInvoiceItems(final LocalDate startDate, final LocalDate endDa final LinkedHashMap expectedDates) throws InvalidDateSequenceException { final DefaultBillingModeGenerator billingMode = new DefaultBillingModeGenerator(); - final List invoiceItems = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ADVANCE); - + final RecurringInvoiceItemDataWithNextBillingCycleDate invoiceItemsWithDates = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ADVANCE); + final List invoiceItems = invoiceItemsWithDates.getItemData(); int i = 0; for (final LocalDate periodStartDate : expectedDates.keySet()) { Assert.assertEquals(invoiceItems.get(i).getStartDate(), periodStartDate); diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java index aa4733e117..69a72aae05 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java @@ -153,7 +153,8 @@ private void verifyInvoiceItems(final LocalDate startDate, final LocalDate endDa final LinkedHashMap expectedDates) throws InvalidDateSequenceException { final DefaultBillingModeGenerator billingMode = new DefaultBillingModeGenerator(); - final List invoiceItems = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ARREAR); + final RecurringInvoiceItemDataWithNextBillingCycleDate invoiceItemsWithDates = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ARREAR); + final List invoiceItems = invoiceItemsWithDates.getItemData(); int i = 0; for (final LocalDate periodStartDate : expectedDates.keySet()) { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java index 82e110aac2..2a31e14b67 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java @@ -30,6 +30,7 @@ import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.InvalidDateSequenceException; import org.killbill.billing.invoice.model.RecurringInvoiceItemData; +import org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; @@ -72,10 +73,10 @@ protected void testCalculateNumberOfBillingCycles(final LocalDate startDate, fin } protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException { - final List items = getBillingModeGenerator().generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); + final RecurringInvoiceItemDataWithNextBillingCycleDate items = getBillingModeGenerator().generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); BigDecimal numberOfBillingCycles = ZERO; - for (final RecurringInvoiceItemData item : items) { + for (final RecurringInvoiceItemData item : items.getItemData()) { numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles()); } @@ -83,10 +84,10 @@ protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, f } protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException { - final List items = getBillingModeGenerator().generateInvoiceItemData(startDate, null, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); + final RecurringInvoiceItemDataWithNextBillingCycleDate items = getBillingModeGenerator().generateInvoiceItemData(startDate, null, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); BigDecimal numberOfBillingCycles = ZERO; - for (final RecurringInvoiceItemData item : items) { + for (final RecurringInvoiceItemData item : items.getItemData()) { numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles()); } From a75db153b7013ea05490c8dc78bcf0c5647fa5a3 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 2 Sep 2015 11:13:42 -0700 Subject: [PATCH 049/137] payment: Increment # tries for global lock to match the new the default 100 mSec timeout We want to at least wait for a 5 sec before the timeout so the value needs to be bumped to 50. --- .../java/org/killbill/billing/invoice/InvoiceDispatcher.java | 5 ++++- .../org/killbill/billing/invoice/api/InvoiceApiHelper.java | 3 ++- .../org/killbill/billing/payment/core/ProcessorBase.java | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java index d229093938..ddfa83ed35 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java @@ -113,7 +113,10 @@ public class InvoiceDispatcher { private static final Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class); - private static final int NB_LOCK_TRY = 5; + + + // 50 * 100ms = 5sec + private static final int NB_LOCK_TRY = 50; private static final Ordering UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural(); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java index 9506d02d08..b56af28664 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java @@ -57,7 +57,8 @@ public class InvoiceApiHelper { private static final Logger log = LoggerFactory.getLogger(InvoiceApiHelper.class); - private static final int NB_LOCK_TRY = 5; + // 50 * 100ms = 5sec + private static final int NB_LOCK_TRY = 50; private final InvoicePluginDispatcher invoicePluginDispatcher; private final InvoiceDao dao; diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java index e657269aca..3b76d67891 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java @@ -63,7 +63,8 @@ public abstract class ProcessorBase { - public static final int NB_LOCK_TRY = 5; + // 50 * 100ms = 5sec + public static final int NB_LOCK_TRY = 50; protected final OSGIServiceRegistration pluginRegistry; protected final AccountInternalApi accountInternalApi; From e9c13236d38ec7e02752e6b85e8ebea299702759 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 2 Sep 2015 14:43:14 -0700 Subject: [PATCH 050/137] junction: Modify BillingEvent api Remove unused BillingPeriod, and make sure to remove usage sections when the subscription has been cancelled. Please enter the commit message for your changes. Lines starting --- .../billing/junction/BillingEvent.java | 35 ++++++------- .../generator/DefaultInvoiceGenerator.java | 2 +- .../billing/invoice/TestInvoiceHelper.java | 5 -- .../plumbing/billing/BlockingCalculator.java | 11 ++-- .../plumbing/billing/DefaultBillingEvent.java | 51 +++++++------------ .../plumbing/billing/TestBillingApi.java | 1 - .../billing/TestBlockingCalculator.java | 11 ++-- .../billing/TestDefaultBillingEvent.java | 5 +- 8 files changed, 45 insertions(+), 76 deletions(-) diff --git a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java index cfc0ea4292..02c15d08b9 100644 --- a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java +++ b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java @@ -37,83 +37,78 @@ public interface BillingEvent extends Comparable { /** * @return the account that this billing event is associated with */ - public Account getAccount(); + Account getAccount(); /** * @return the billCycleDay in the account timezone as seen for that subscription at that time *

* Note: The billCycleDay may come from the Account, or the bundle or the subscription itself */ - public int getBillCycleDayLocal(); + int getBillCycleDayLocal(); /** * @return the subscription */ - public SubscriptionBase getSubscription(); + SubscriptionBase getSubscription(); /** * @return the date for when that event became effective */ - public DateTime getEffectiveDate(); + DateTime getEffectiveDate(); /** * @return the plan phase */ - public PlanPhase getPlanPhase(); + PlanPhase getPlanPhase(); /** * @return the plan */ - public Plan getPlan(); + Plan getPlan(); /** * @return the billing period for the active phase */ - public BillingPeriod getBillingPeriod(); - - /** - * @return the billing mode for the current event - */ - public BillingMode getBillingMode(); + BillingPeriod getBillingPeriod(); /** * @return the description of the billing event */ - public String getDescription(); + String getDescription(); /** * @return the fixed price for the phase */ - public BigDecimal getFixedPrice(); + BigDecimal getFixedPrice(); /** * @return the recurring price for the phase */ - public BigDecimal getRecurringPrice(); + BigDecimal getRecurringPrice(); /** * @return the currency for the account being invoiced */ - public Currency getCurrency(); + Currency getCurrency(); /** * @return the transition type of the underlying subscription event that triggered this */ - public SubscriptionBaseTransitionType getTransitionType(); + SubscriptionBaseTransitionType getTransitionType(); /** * @return a unique long indicating the ordering on which events got inserted on disk-- used for sorting only */ - public Long getTotalOrdering(); + Long getTotalOrdering(); /** * @return the TimeZone of the account */ - public DateTimeZone getTimeZone(); + DateTimeZone getTimeZone(); /** * * @return the list of {@code Usage} section */ - public List getUsages(); + List getUsages(); } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index 3d8fecc5a1..b823655ee5 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -372,7 +372,7 @@ private List processRecurringEvent(final UUID invoiceId, final UUID items.add(recurringItem); } } - updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, thisEvent.getBillingMode(), perSubscriptionFutureNotificationDate); + updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate); } } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java index 9d8dc34633..18d1ec73a3 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java @@ -366,11 +366,6 @@ public BillingPeriod getBillingPeriod() { return billingPeriod; } - @Override - public BillingMode getBillingMode() { - return billingMode; - } - @Override public String getDescription() { return description; diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java index 270e706d94..a55956e17e 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java @@ -32,7 +32,6 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalTenantContext; -import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.catalog.api.Plan; @@ -204,14 +203,13 @@ protected BillingEvent createNewDisableEvent(final DateTime odEventTime, final B final Currency currency = previousEvent.getCurrency(); final String description = ""; - final BillingMode billingMode = previousEvent.getBillingMode(); final SubscriptionBaseTransitionType type = SubscriptionBaseTransitionType.START_BILLING_DISABLED; final Long totalOrdering = globaltotalOrder.getAndIncrement(); final DateTimeZone tz = previousEvent.getTimeZone(); - return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase, + return new DefaultBillingEvent(account, subscription, effectiveDate, true, plan, planPhase, fixedPrice, recurringPrice, currency, - billingPeriod, billCycleDay, billingMode, + billingPeriod, billCycleDay, description, totalOrdering, type, tz); } @@ -227,15 +225,14 @@ protected BillingEvent createNewReenableEvent(final DateTime odEventTime, final final BigDecimal recurringPrice = previousEvent.getRecurringPrice(); final Currency currency = previousEvent.getCurrency(); final String description = ""; - final BillingMode billingMode = previousEvent.getBillingMode(); final BillingPeriod billingPeriod = previousEvent.getBillingPeriod(); final SubscriptionBaseTransitionType type = SubscriptionBaseTransitionType.END_BILLING_DISABLED; final Long totalOrdering = globaltotalOrder.getAndIncrement(); final DateTimeZone tz = previousEvent.getTimeZone(); - return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase, + return new DefaultBillingEvent(account, subscription, effectiveDate, true, plan, planPhase, fixedPrice, recurringPrice, currency, - billingPeriod, billCycleDay, billingMode, + billingPeriod, billCycleDay, description, totalOrdering, type, tz); } diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java index 54cffc5c65..1a7f98dadf 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java @@ -17,7 +17,6 @@ package org.killbill.billing.junction.plumbing.billing; import java.math.BigDecimal; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -25,9 +24,7 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; - import org.killbill.billing.account.api.Account; -import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.catalog.api.Catalog; import org.killbill.billing.catalog.api.CatalogApiException; @@ -35,14 +32,16 @@ import org.killbill.billing.catalog.api.Plan; import org.killbill.billing.catalog.api.PlanPhase; import org.killbill.billing.catalog.api.Usage; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.subscription.api.SubscriptionBase; import org.killbill.billing.events.EffectiveSubscriptionInternalEvent; import org.killbill.billing.junction.BillingEvent; +import org.killbill.billing.subscription.api.SubscriptionBase; +import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; public class DefaultBillingEvent implements BillingEvent { + private final Account account; private final int billCycleDayLocal; private final SubscriptionBase subscription; @@ -53,7 +52,6 @@ public class DefaultBillingEvent implements BillingEvent { private final BigDecimal recurringPrice; private final Currency currency; private final String description; - private final BillingMode billingMode; private final BillingPeriod billingPeriod; private final SubscriptionBaseTransitionType type; private final Long totalOrdering; @@ -63,16 +61,16 @@ public class DefaultBillingEvent implements BillingEvent { public DefaultBillingEvent(final Account account, final EffectiveSubscriptionInternalEvent transition, final SubscriptionBase subscription, final int billCycleDayLocal, final Currency currency, final Catalog catalog) throws CatalogApiException { + final boolean isActive = transition.getTransitionType() != SubscriptionBaseTransitionType.CANCEL; + this.account = account; this.billCycleDayLocal = billCycleDayLocal; this.subscription = subscription; this.effectiveDate = transition.getEffectiveTransitionTime(); - final String planPhaseName = (transition.getTransitionType() != SubscriptionBaseTransitionType.CANCEL) ? - transition.getNextPhase() : transition.getPreviousPhase(); + final String planPhaseName = isActive ? transition.getNextPhase() : transition.getPreviousPhase(); this.planPhase = (planPhaseName != null) ? catalog.findPhase(planPhaseName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null; - final String planName = (transition.getTransitionType() != SubscriptionBaseTransitionType.CANCEL) ? - transition.getNextPlan() : transition.getPreviousPlan(); + final String planName = isActive ? transition.getNextPlan() : transition.getPreviousPlan(); this.plan = (planName != null) ? catalog.findPlan(planName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null; final String nextPhaseName = transition.getNextPhase(); @@ -81,24 +79,22 @@ public DefaultBillingEvent(final Account account, final EffectiveSubscriptionInt final String prevPhaseName = transition.getPreviousPhase(); final PlanPhase prevPhase = (prevPhaseName != null) ? catalog.findPhase(prevPhaseName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null; - this.fixedPrice = getFixedPrice(nextPhase, currency); this.recurringPrice = getRecurringPrice(nextPhase, currency); this.currency = currency; this.description = transition.getTransitionType().toString(); - this.billingMode = BillingMode.IN_ADVANCE; - this.billingPeriod = getRecurringBillingPeriod((transition.getTransitionType() != SubscriptionBaseTransitionType.CANCEL) ? nextPhase : prevPhase); + this.billingPeriod = getRecurringBillingPeriod(isActive ? nextPhase : prevPhase); this.type = transition.getTransitionType(); this.totalOrdering = transition.getTotalOrdering(); this.timeZone = account.getTimeZone(); - this.usages = initializeUsage(); + this.usages = initializeUsage(isActive); } - - public DefaultBillingEvent(final Account account, final SubscriptionBase subscription, final DateTime effectiveDate, final Plan plan, final PlanPhase planPhase, + public DefaultBillingEvent(final Account account, final SubscriptionBase subscription, final DateTime effectiveDate, final boolean isActive, + final Plan plan, final PlanPhase planPhase, final BigDecimal fixedPrice, final BigDecimal recurringPrice, final Currency currency, - final BillingPeriod billingPeriod, final int billCycleDayLocal, final BillingMode billingMode, + final BillingPeriod billingPeriod, final int billCycleDayLocal, final String description, final long totalOrdering, final SubscriptionBaseTransitionType type, final DateTimeZone timeZone) { this.account = account; this.subscription = subscription; @@ -110,12 +106,11 @@ public DefaultBillingEvent(final Account account, final SubscriptionBase subscri this.currency = currency; this.billingPeriod = billingPeriod; this.billCycleDayLocal = billCycleDayLocal; - this.billingMode = billingMode; this.description = description; this.type = type; this.totalOrdering = totalOrdering; this.timeZone = timeZone; - this.usages = initializeUsage(); + this.usages = initializeUsage(isActive); } @@ -200,11 +195,6 @@ public BillingPeriod getBillingPeriod() { return billingPeriod; } - @Override - public BillingMode getBillingMode() { - return billingMode; - } - @Override public String getDescription() { return description; @@ -245,7 +235,6 @@ public List getUsages() { return usages; } - @Override public String toString() { // Note: we don't use all fields here, as the output would be overwhelming @@ -279,9 +268,6 @@ public boolean equals(final Object o) { if (account != null ? !account.equals(that.account) : that.account != null) { return false; } - if (billingMode != that.billingMode) { - return false; - } if (billingPeriod != that.billingPeriod) { return false; } @@ -334,7 +320,6 @@ public int hashCode() { result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0); result = 31 * result + (currency != null ? currency.hashCode() : 0); result = 31 * result + (description != null ? description.hashCode() : 0); - result = 31 * result + (billingMode != null ? billingMode.hashCode() : 0); result = 31 * result + (billingPeriod != null ? billingPeriod.hashCode() : 0); result = 31 * result + (type != null ? type.hashCode() : 0); result = 31 * result + (totalOrdering != null ? totalOrdering.hashCode() : 0); @@ -342,7 +327,6 @@ public int hashCode() { return result; } - private BigDecimal getFixedPrice(@Nullable final PlanPhase nextPhase, final Currency currency) throws CatalogApiException { return (nextPhase != null && nextPhase.getFixed() != null && nextPhase.getFixed().getPrice() != null) ? nextPhase.getFixed().getPrice().getPrice(currency) : null; } @@ -358,8 +342,11 @@ private BillingPeriod getRecurringBillingPeriod(@Nullable final PlanPhase nextPh return nextPhase.getRecurring() != null ? nextPhase.getRecurring().getBillingPeriod() : BillingPeriod.NO_BILLING_PERIOD; } - private List initializeUsage() { - List result = Collections.emptyList(); + private List initializeUsage(final boolean isActive) { + List result = ImmutableList.of(); + if (!isActive) { + return result; + } if (planPhase != null) { result = Lists.newArrayList(); for (Usage usage : planPhase.getUsages()) { diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java index 4acd3e2cd3..34ab02f709 100644 --- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java +++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java @@ -255,7 +255,6 @@ private void checkEvent(final BillingEvent event, final Plan nextPlan, final int if (!SubscriptionBaseTransitionType.START_BILLING_DISABLED.equals(event.getTransitionType())) { Assert.assertEquals(nextPhase.getRecurring().getBillingPeriod(), event.getBillingPeriod()); } - Assert.assertEquals(BillingMode.IN_ADVANCE, event.getBillingMode()); Assert.assertEquals(desc, event.getTransitionType().toString()); } diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java index 867ba4f956..cf02a82607 100644 --- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java +++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java @@ -532,14 +532,13 @@ protected BillingEvent createRealEvent(final DateTime effectiveDate, final Subsc final BigDecimal recurringPrice = BigDecimal.TEN; final Currency currency = Currency.USD; final String description = ""; - final BillingMode billingModeType = BillingMode.IN_ADVANCE; final BillingPeriod billingPeriod = BillingPeriod.MONTHLY; final Long totalOrdering = 0L; final DateTimeZone tz = DateTimeZone.UTC; - return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase, + return new DefaultBillingEvent(account, subscription, effectiveDate, true, plan, planPhase, fixedPrice, recurringPrice, currency, - billingPeriod, billCycleDay, billingModeType, + billingPeriod, billCycleDay, description, totalOrdering, type, tz); } @@ -578,7 +577,6 @@ public void testCreateNewDisableEvent() { assertNull(result.getRecurringPrice()); assertEquals(result.getCurrency(), event.getCurrency()); assertEquals(result.getDescription(), ""); - assertEquals(result.getBillingMode(), event.getBillingMode()); assertEquals(result.getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD); assertEquals(result.getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED); // TODO - ugly, fragile @@ -599,7 +597,6 @@ public void testCreateNewReenableEvent() { assertEquals(result.getRecurringPrice(), event.getRecurringPrice()); assertEquals(result.getCurrency(), event.getCurrency()); assertEquals(result.getDescription(), ""); - assertEquals(result.getBillingMode(), event.getBillingMode()); assertEquals(result.getBillingPeriod(), event.getBillingPeriod()); assertEquals(result.getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED); // TODO - ugly, fragile @@ -609,8 +606,8 @@ public void testCreateNewReenableEvent() { private class MockBillingEvent extends DefaultBillingEvent { public MockBillingEvent() { - super(account, subscription1, clock.getUTCNow(), null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL, - 4, BillingMode.IN_ADVANCE, "", 3L, SubscriptionBaseTransitionType.CREATE, DateTimeZone.UTC); + super(account, subscription1, clock.getUTCNow(), true, null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL, + 4, "", 3L, SubscriptionBaseTransitionType.CREATE, DateTimeZone.UTC); } } diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java index 79b50eaceb..a6a212504c 100644 --- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java +++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java @@ -26,7 +26,6 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.killbill.billing.catalog.api.BillingMode; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.Test; @@ -187,10 +186,10 @@ private BillingEvent createEvent(final SubscriptionBase sub, final DateTime effe final PlanPhase shotgunMonthly = createMockMonthlyPlanPhase(null, BigDecimal.ZERO, PhaseType.TRIAL); final Account account = new MockAccountBuilder().build(); - return new DefaultBillingEvent(account, sub, effectiveDate, + return new DefaultBillingEvent(account, sub, effectiveDate, true, shotgun, shotgunMonthly, BigDecimal.ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, billCycleDay, - BillingMode.IN_ADVANCE, "Test Event 1", totalOrdering, type, DateTimeZone.UTC); + "Test Event 1", totalOrdering, type, DateTimeZone.UTC); } private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate, From 41bfa0aaa3defd77c0d1c91f3cd8befafa16d1f9 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 2 Sep 2015 18:17:05 -0700 Subject: [PATCH 051/137] invoice: Rework next notification date for in arrear usage billing. The previous code would use the endDate of the latest (in arrear usage) item, and apply some heurstics based on that date to figureb out the next notification date associated to that specific usage section in a specific subscription. Instead, we now compute thgat logic from the in arrear usage code where all the information is already available. --- .../billing/invoice/InvoiceDispatcher.java | 24 ++------- .../generator/DefaultInvoiceGenerator.java | 39 ++++++++------- .../generator/InvoiceWithMetadata.java | 33 +++++-------- .../model/DefaultBillingModeGenerator.java | 6 +-- ...voiceItemDataWithNextBillingCycleDate.java | 4 +- .../ContiguousIntervalConsumableInArrear.java | 49 ++++++++++++++++++- .../usage/SubscriptionConsumableInArrear.java | 49 +++++++++++++++++-- ...tContiguousIntervalConsumableInArrear.java | 18 ++++--- .../TestSubscriptionConsumableInArrear.java | 6 +-- .../invoice/usage/TestUsageInArrearBase.java | 4 +- .../billing/TestBlockingCalculator.java | 1 - 11 files changed, 150 insertions(+), 83 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java index 58194a67cc..6dabd5cf6b 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java @@ -39,12 +39,10 @@ import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.BillingActionPolicy; -import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.CatalogApiException; import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.catalog.api.PlanPhasePriceOverride; import org.killbill.billing.catalog.api.PlanPhaseSpecifier; -import org.killbill.billing.catalog.api.Usage; import org.killbill.billing.entitlement.api.SubscriptionEventType; import org.killbill.billing.events.BusInternalEvent; import org.killbill.billing.events.EffectiveSubscriptionInternalEvent; @@ -66,7 +64,6 @@ import org.killbill.billing.invoice.dao.InvoiceDao; import org.killbill.billing.invoice.dao.InvoiceItemModelDao; import org.killbill.billing.invoice.dao.InvoiceModelDao; -import org.killbill.billing.invoice.generator.BillingIntervalDetail; import org.killbill.billing.invoice.generator.InvoiceGenerator; import org.killbill.billing.invoice.generator.InvoiceWithMetadata; import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; @@ -77,7 +74,6 @@ import org.killbill.billing.invoice.model.RecurringInvoiceItem; import org.killbill.billing.invoice.notification.DefaultNextBillingDateNotifier; import org.killbill.billing.invoice.notification.NextBillingDateNotificationKey; -import org.killbill.billing.junction.BillingEvent; import org.killbill.billing.junction.BillingEventSet; import org.killbill.billing.junction.BillingInternalApi; import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi; @@ -272,7 +268,7 @@ public Invoice apply(final InvoiceModelDao input) { final Invoice invoice = invoiceWithMetadata.getInvoice(); // Compute future notifications - final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, billingEvents, dateAndTimeZoneContext, context); + final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, dateAndTimeZoneContext, context); // @@ -343,7 +339,7 @@ public Invoice apply(final InvoiceModelDao input) { } - private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext, final InternalCallContext context) { + private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final DateAndTimeZoneContext dateAndTimeZoneContext, final InternalCallContext context) { final Map> result = new HashMap>(); @@ -360,7 +356,7 @@ private FutureAccountNotifications createNextFutureNotificationDate(final Invoic if (subscriptionFutureNotificationDates.getNextUsageDates() != null) { for (UsageDef usageDef : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) { final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageDef); - final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageDef.getUsageName(), nextNotificationDateForUsage, dateAndTimeZoneContext, billingEvents); + final DateTime subscriptionUsageCallbackDate = nextNotificationDateForUsage != null ? dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(nextNotificationDateForUsage) : null; perSubscriptionNotifications.add(new SubscriptionNotification(subscriptionUsageCallbackDate, true)); } } @@ -496,20 +492,6 @@ private CallContext buildCallContext(final InternalCallContext context) { return internalCallContextFactory.createCallContext(context); } - private DateTime getNextUsageBillingDate(final UUID subscriptionId, final String usageName, final LocalDate chargedThroughDate, final DateAndTimeZoneContext dateAndTimeZoneContext, final BillingEventSet billingEvents) { - - final Usage usage = billingEvents.getUsages().get(usageName); - final BillingEvent billingEventSubscription = Iterables.tryFind(billingEvents, new Predicate() { - @Override - public boolean apply(@Nullable final BillingEvent input) { - return input.getSubscription().getId().equals(subscriptionId); - } - }).orNull(); - - final LocalDate nextCallbackUsageDate = (usage.getBillingMode() == BillingMode.IN_ARREAR) ? BillingIntervalDetail.alignProposedBillCycleDate(chargedThroughDate.plusMonths(usage.getBillingPeriod().getNumberOfMonths()), billingEventSubscription.getBillCycleDayLocal()) : chargedThroughDate; - return dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(nextCallbackUsageDate); - } - private void setChargedThroughDates(final DateAndTimeZoneContext dateAndTimeZoneContext, final Collection fixedPriceItems, final Collection recurringItems, diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index b823655ee5..8fdc8bcd1c 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -57,6 +57,7 @@ import org.killbill.billing.invoice.usage.RawUsageOptimizer; import org.killbill.billing.invoice.usage.RawUsageOptimizer.RawUsageOptimizerResult; import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear; +import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear.SubscriptionConsumableInArrearItemsAndNextNotificationDate; import org.killbill.billing.junction.BillingEvent; import org.killbill.billing.junction.BillingEventSet; import org.killbill.billing.util.config.InvoiceConfig; @@ -66,8 +67,6 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -164,9 +163,10 @@ public boolean apply(@Nullable final Usage input) { final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); - final List newInArrearUsageItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + final List newInArrearUsageItems = subscriptionResult.getInvoiceItems(); items.addAll(newInArrearUsageItems); - updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newInArrearUsageItems, BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); + updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); curEvents = Lists.newArrayList(); } curSubscriptionId = subscriptionId; @@ -176,9 +176,10 @@ public boolean apply(@Nullable final Usage input) { final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); - final List newInArrearUsageItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + final List newInArrearUsageItems = subscriptionResult.getInvoiceItems(); items.addAll(newInArrearUsageItems); - updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newInArrearUsageItems, BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); + updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); } return items; @@ -187,19 +188,21 @@ public boolean apply(@Nullable final Usage input) { } } + private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final Map nextBillingCycleDates, final BillingMode usageBillingMode, final Map perSubscriptionFutureNotificationDates) { + if (usageBillingMode == BillingMode.IN_ADVANCE) { + throw new IllegalStateException("Not implemented Yet)"); + } - private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final List newInArrearUsageItems, final BillingMode usageBillingMode, final Map perSubscriptionFutureNotificationDates) { - for (final InvoiceItem item : newInArrearUsageItems) { - SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); - if (subscriptionFutureNotificationDates == null) { - subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(null); - perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); - } - subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(item.getUsageName(), usageBillingMode, item.getEndDate()); + SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); + if (subscriptionFutureNotificationDates == null) { + subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(null); + perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); + } + for (final String usageName : nextBillingCycleDates.keySet()) { + subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(usageName, usageBillingMode, nextBillingCycleDates.get(usageName)); } } - private Map> extractPerSubscriptionExistingConsumableInArrearUsageItems(final Map knownUsage, @Nullable final List existingInvoices) { if (existingInvoices == null || existingInvoices.isEmpty()) { @@ -317,7 +320,6 @@ private List processRecurringBillingEvents(final UUID invoiceId, fi return proposedItems; } - private List processFixedBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List proposedItems) { final Iterator eventIt = events.iterator(); while (eventIt.hasNext()) { @@ -390,7 +392,7 @@ private List processRecurringEvent(final UUID invoiceId, final UUID private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List newProposedItems, final BillingMode billingMode, final Map perSubscriptionFutureNotificationDates) { LocalDate nextNotificationDate = null; - switch(billingMode) { + switch (billingMode) { case IN_ADVANCE: for (final InvoiceItem item : newProposedItems) { if ((item.getEndDate() != null) && @@ -421,9 +423,8 @@ private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId } } - private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, - final LocalDate targetDate, final Currency currency) { + final LocalDate targetDate, final Currency currency) { final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); if (roundedStartDate.isAfter(targetDate)) { diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java index 0e0f30e9e4..50f41da1b1 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java @@ -18,7 +18,6 @@ package org.killbill.billing.invoice.generator; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.UUID; @@ -29,7 +28,6 @@ import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceItem; import org.killbill.billing.invoice.api.InvoiceItemType; -import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates.UsageDef; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; @@ -39,7 +37,6 @@ public class InvoiceWithMetadata { private final Invoice invoice; private final Map perSubscriptionFutureNotificationDates; - public InvoiceWithMetadata(final Invoice invoice, final Map perSubscriptionFutureNotificationDates) { this.invoice = invoice; this.perSubscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates; @@ -56,20 +53,14 @@ public Map getPerSubscriptionFutureNo // Remove all the IN_ADVANCE items for which we have no invoice items private void build() { + // nextRecurringDate are computed based on *proposed* items, and not missing items (= proposed - existing). So + // we need to filter out the dates for which there is no item left otherwsie we may end up in creating too many notification dates + // and in particular that could lead to an infinite loop. for (final UUID subscriptionId : perSubscriptionFutureNotificationDates.keySet()) { final SubscriptionFutureNotificationDates tmp = perSubscriptionFutureNotificationDates.get(subscriptionId); if (tmp.getRecurringBillingMode() == BillingMode.IN_ADVANCE && !hasItemsForSubscription(subscriptionId, InvoiceItemType.RECURRING)) { tmp.resetNextRecurringDate(); } - if (tmp.getNextUsageDates() != null) { - final Iterator it = tmp.getNextUsageDates().keySet().iterator(); - while (it.hasNext()) { - final UsageDef usageDef = it.next(); - if (usageDef.getBillingMode() == BillingMode.IN_ADVANCE && !hasItemsForSubscription(subscriptionId, InvoiceItemType.USAGE)) { - it.remove(); - } - } - } } } @@ -97,16 +88,20 @@ public SubscriptionFutureNotificationDates(final BillingMode recurringBillingMod } public void updateNextRecurringDateIfRequired(final LocalDate nextRecurringDateCandidate) { - nextRecurringDate = getMaxDate(nextRecurringDate, nextRecurringDateCandidate); + if (nextRecurringDateCandidate != null) { + nextRecurringDate = getMaxDate(nextRecurringDate, nextRecurringDateCandidate); + } } public void updateNextUsageDateIfRequired(final String usageName, final BillingMode billingMode, final LocalDate nextUsageDateCandidate) { - if (nextUsageDates == null) { - nextUsageDates = new HashMap(); + if (nextUsageDateCandidate != null) { + if (nextUsageDates == null) { + nextUsageDates = new HashMap(); + } + final UsageDef usageDef = new UsageDef(usageName, billingMode); + final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageDef), nextUsageDateCandidate); + nextUsageDates.put(usageDef, nextUsageDate); } - final UsageDef usageDef = new UsageDef(usageName, billingMode); - final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageDef), nextUsageDateCandidate); - nextUsageDates.put(usageDef, nextUsageDate); } public LocalDate getNextRecurringDate() { @@ -125,7 +120,6 @@ public void resetNextRecurringDate() { nextRecurringDate = null; } - private static LocalDate getMaxDate(@Nullable final LocalDate existingDate, final LocalDate nextDateCandidate) { if (existingDate == null) { return nextDateCandidate; @@ -134,7 +128,6 @@ private static LocalDate getMaxDate(@Nullable final LocalDate existingDate, fina } } - public static class UsageDef { private final String usageName; diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java index 2da91696a5..db75fef506 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java @@ -56,7 +56,7 @@ public RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData( // We are not billing for less than a day if (!billingIntervalDetail.hasSomethingToBill()) { - return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode); + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); } // // If there is an endDate and that endDate is before our first coming firstBillingCycleDate, all we have to do @@ -66,7 +66,7 @@ public RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData( final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod); final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods); results.add(itemData); - return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode); + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); } // @@ -126,6 +126,6 @@ public RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData( results.add(itemData); } } - return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode); + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); } } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java index 3533240281..97e1b67562 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java @@ -27,12 +27,10 @@ public class RecurringInvoiceItemDataWithNextBillingCycleDate { private final List itemData; private final BillingIntervalDetail billingIntervalDetail; - private final BillingMode billingMode; - public RecurringInvoiceItemDataWithNextBillingCycleDate(final List itemData, final BillingIntervalDetail billingIntervalDetail,final BillingMode billingMode) { + public RecurringInvoiceItemDataWithNextBillingCycleDate(final List itemData, final BillingIntervalDetail billingIntervalDetail) { this.itemData = itemData; this.billingIntervalDetail = billingIntervalDetail; - this.billingMode = billingMode; } public List getItemData() { diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java index a7d78e4456..5cbb2236b0 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java @@ -28,6 +28,7 @@ import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; +import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.CatalogApiException; import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.catalog.api.TieredBlock; @@ -127,6 +128,25 @@ public ContiguousIntervalConsumableInArrear build(final boolean closedInterval) return this; } + public class ConsumableInArrearItemsAndNextNotificationDate { + private final List invoiceItems; + private final LocalDate nextNotificationDate; + + public ConsumableInArrearItemsAndNextNotificationDate(final List invoiceItems, final LocalDate nextNotificationDate) { + this.invoiceItems = invoiceItems; + this.nextNotificationDate = nextNotificationDate; + } + + public List getInvoiceItems() { + return invoiceItems; + } + + public LocalDate getNextNotificationDate() { + return nextNotificationDate; + } + } + + /** * Compute the missing usage invoice items based on what should be billed and what has been billed ($ amount comparison). * @@ -134,12 +154,12 @@ public ContiguousIntervalConsumableInArrear build(final boolean closedInterval) * @return * @throws CatalogApiException */ - public List computeMissingItems(final List existingUsage) throws CatalogApiException { + public ConsumableInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotificationDate(final List existingUsage) throws CatalogApiException { Preconditions.checkState(isBuilt.get()); if (transitionTimes.size() < 2) { - return ImmutableList.of(); + return new ConsumableInArrearItemsAndNextNotificationDate(ImmutableList.of(), null); } final List result = Lists.newLinkedList(); @@ -184,9 +204,34 @@ public List computeMissingItems(final List existingUsa } } } + + final LocalDate nextNotificationdate = computeNextNotificationDate(); + return new ConsumableInArrearItemsAndNextNotificationDate(result, nextNotificationdate); + } + + private LocalDate computeNextNotificationDate() { + LocalDate result = null; + final Iterator eventIt = billingEvents.iterator(); + BillingEvent nextEvent = eventIt.next(); + while (eventIt.hasNext()) { + final BillingEvent thisEvent = nextEvent; + nextEvent = eventIt.next(); + final LocalDate startDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); + final LocalDate endDate = new LocalDate(nextEvent.getEffectiveDate(), nextEvent.getTimeZone()); + + final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, endDate, targetDate, thisEvent.getBillCycleDayLocal(), usage.getBillingPeriod(), BillingMode.IN_ARREAR); + final LocalDate nextBillingCycleDate = bid.getNextBillingCycleDate(); + result = (result == null || result.compareTo(nextBillingCycleDate) < 0) ? nextBillingCycleDate : result; + } + + final LocalDate startDate = new LocalDate(nextEvent.getEffectiveDate(), nextEvent.getTimeZone()); + final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, null, targetDate, nextEvent.getBillCycleDayLocal(), usage.getBillingPeriod(), BillingMode.IN_ARREAR); + final LocalDate nextBillingCycleDate = bid.getNextBillingCycleDate(); + result = (result == null || result.compareTo(nextBillingCycleDate) < 0) ? nextBillingCycleDate : result; return result; } + @VisibleForTesting List getRolledUpUsage() { diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java index 66f7449894..e2b696553d 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java @@ -20,6 +20,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -31,6 +32,7 @@ import org.killbill.billing.catalog.api.Usage; import org.killbill.billing.catalog.api.UsageType; import org.killbill.billing.invoice.api.InvoiceItem; +import org.killbill.billing.invoice.usage.ContiguousIntervalConsumableInArrear.ConsumableInArrearItemsAndNextNotificationDate; import org.killbill.billing.junction.BillingEvent; import org.killbill.billing.usage.RawUsage; @@ -38,6 +40,8 @@ import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; @@ -84,6 +88,7 @@ public boolean apply(final RawUsage input) { })); } + /** * Based on billing events, (@code existingUsage} and targetDate, figure out what remains to be billed. * @@ -91,16 +96,18 @@ public boolean apply(final RawUsage input) { * @return * @throws CatalogApiException */ - public List computeMissingUsageInvoiceItems(final List existingUsage) throws CatalogApiException { + public SubscriptionConsumableInArrearItemsAndNextNotificationDate computeMissingUsageInvoiceItems(final List existingUsage) throws CatalogApiException { - final List result = Lists.newLinkedList(); + final SubscriptionConsumableInArrearItemsAndNextNotificationDate result = new SubscriptionConsumableInArrearItemsAndNextNotificationDate(); final List billingEventTransitionTimePeriods = computeInArrearUsageInterval(); for (ContiguousIntervalConsumableInArrear usageInterval : billingEventTransitionTimePeriods) { - result.addAll(usageInterval.computeMissingItems(existingUsage)); + result.addConsumableInArrearItemsAndNextNotificationDate(usageInterval.getUsage().getName(), usageInterval.computeMissingItemsAndNextNotificationDate(existingUsage)); } return result; } + + @VisibleForTesting List computeInArrearUsageInterval() { @@ -169,4 +176,40 @@ List findConsumableInArrearUsages(final BillingEvent event) { } return result; } + + public class SubscriptionConsumableInArrearItemsAndNextNotificationDate { + private List invoiceItems; + private Map perUsageNotificationDates; + + public SubscriptionConsumableInArrearItemsAndNextNotificationDate() { + this.invoiceItems = null; + this.perUsageNotificationDates = null; + } + + public void addConsumableInArrearItemsAndNextNotificationDate(final String usageName, final ConsumableInArrearItemsAndNextNotificationDate input) { + if (!input.getInvoiceItems().isEmpty()) { + if (invoiceItems == null) { + invoiceItems = new LinkedList(); + } + invoiceItems.addAll(input.getInvoiceItems()); + + } + + if (input.getNextNotificationDate() != null) { + if (perUsageNotificationDates == null) { + perUsageNotificationDates = new HashMap(); + } + perUsageNotificationDates.put(usageName, input.getNextNotificationDate()); + } + } + + public List getInvoiceItems() { + return invoiceItems != null ? invoiceItems : ImmutableList.of(); + } + + public Map getPerUsageNotificationDates() { + return perUsageNotificationDates != null ? perUsageNotificationDates : ImmutableMap.of(); + } + } + } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java index c59589f0cd..dd025c0559 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java @@ -35,6 +35,7 @@ import org.killbill.billing.invoice.api.InvoiceItem; import org.killbill.billing.invoice.model.FixedPriceInvoiceItem; import org.killbill.billing.invoice.model.UsageInvoiceItem; +import org.killbill.billing.invoice.usage.ContiguousIntervalConsumableInArrear.ConsumableInArrearItemsAndNextNotificationDate; import org.killbill.billing.junction.BillingEvent; import org.killbill.billing.usage.RawUsage; import org.killbill.billing.usage.api.RolledUpUsage; @@ -88,6 +89,7 @@ public void testComputeToBeBilledUsage() { final LocalDate targetDate = startDate.plusDays(1); final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.of(), targetDate, false, createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), + BillingPeriod.MONTHLY, Collections.emptyList()) ); @@ -127,6 +129,7 @@ public void testComputeBilledUsage() throws CatalogApiException { final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.of(), targetDate, false, createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), + BillingPeriod.MONTHLY, Collections.emptyList()) ); @@ -156,8 +159,8 @@ public void testComputeMissingItems() throws CatalogApiException { final LocalDate targetDate = endDate; - final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.emptyList()); - final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.emptyList()); + final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.emptyList()); + final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.emptyList()); final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, event1, event2); @@ -168,7 +171,8 @@ public void testComputeMissingItems() throws CatalogApiException { final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency); invoiceItems.add(ii2); - final List rawResults = intervalConsumableInArrear.computeMissingItems(invoiceItems); + final ConsumableInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems); + final List rawResults = usageResult.getInvoiceItems(); assertEquals(rawResults.size(), 4); final List result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate() { @@ -216,16 +220,16 @@ public void testGetRolledUpUsage() { final LocalDate t0 = new LocalDate(2015, 03, BCD); - final BillingEvent eventT0 = createMockBillingEvent(t0.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.emptyList()); + final BillingEvent eventT0 = createMockBillingEvent(t0.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.emptyList()); final LocalDate t1 = new LocalDate(2015, 04, BCD); - final BillingEvent eventT1 = createMockBillingEvent(t1.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.emptyList()); + final BillingEvent eventT1 = createMockBillingEvent(t1.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.emptyList()); final LocalDate t2 = new LocalDate(2015, 05, BCD); - final BillingEvent eventT2 = createMockBillingEvent(t2.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.emptyList()); + final BillingEvent eventT2 = createMockBillingEvent(t2.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.emptyList()); final LocalDate t3 = new LocalDate(2015, 06, BCD); - final BillingEvent eventT3 = createMockBillingEvent(t3.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.emptyList()); + final BillingEvent eventT3 = createMockBillingEvent(t3.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.emptyList()); final LocalDate targetDate = t3; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java index 7434bd8e8b..a2d90d936e 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java @@ -60,15 +60,15 @@ public void testComputeInArrearUsageInterval() { final Usage usage2 = createDefaultUsage(usageName2, BillingPeriod.MONTHLY, tier2); final DateTime dt1 = new DateTime(2013, 3, 23, 4, 34, 59, DateTimeZone.UTC); - final BillingEvent evt1 = createMockBillingEvent(dt1, ImmutableList.builder().add(usage1).add(usage2).build()); + final BillingEvent evt1 = createMockBillingEvent(dt1, BillingPeriod.MONTHLY, ImmutableList.builder().add(usage1).add(usage2).build()); billingEvents.add(evt1); final DateTime dt2 = new DateTime(2013, 4, 23, 4, 34, 59, DateTimeZone.UTC); - final BillingEvent evt2 = createMockBillingEvent(dt2, ImmutableList.builder().add(usage1).build()); + final BillingEvent evt2 = createMockBillingEvent(dt2, BillingPeriod.MONTHLY, ImmutableList.builder().add(usage1).build()); billingEvents.add(evt2); final DateTime dt3 = new DateTime(2013, 5, 23, 4, 34, 59, DateTimeZone.UTC); - final BillingEvent evt3 = createMockBillingEvent(dt3, ImmutableList.builder().add(usage1).add(usage2).build()); + final BillingEvent evt3 = createMockBillingEvent(dt3, BillingPeriod.MONTHLY, ImmutableList.builder().add(usage1).add(usage2).build()); billingEvents.add(evt3); LocalDate targetDate = new LocalDate(2013, 6, 23); diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java index 6a61d18932..616e852d7e 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java @@ -109,12 +109,14 @@ protected DefaultTieredBlock createDefaultTieredBlock(final String unit, final i return block; } - protected BillingEvent createMockBillingEvent(DateTime effectiveDate, final List usages) { + protected BillingEvent createMockBillingEvent(DateTime effectiveDate, BillingPeriod billingPeriod, final List usages) { final BillingEvent result = Mockito.mock(BillingEvent.class); Mockito.when(result.getCurrency()).thenReturn(Currency.BTC); Mockito.when(result.getBillCycleDayLocal()).thenReturn(BCD); Mockito.when(result.getTimeZone()).thenReturn(DateTimeZone.UTC); Mockito.when(result.getEffectiveDate()).thenReturn(effectiveDate); + Mockito.when(result.getBillingPeriod()).thenReturn(billingPeriod); + final Account account = Mockito.mock(Account.class); Mockito.when(account.getId()).thenReturn(accountId); diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java index cf02a82607..b2e8ce6308 100644 --- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java +++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java @@ -28,7 +28,6 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; -import org.killbill.billing.catalog.api.BillingMode; import org.mockito.Mockito; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; From 7d8f44cf5d4536d4f99b1635e61b1f5c1e4b2dd6 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 2 Sep 2015 18:20:10 -0700 Subject: [PATCH 052/137] invoice: Fix issue for in arrear usage billing where subscription cancellation date is not correctly respected. Also add a beatrix test case and fix existing unit test to verify the new behavior. --- .../usage/TestConsumableInArrear.java | 64 ++++++++++++++++++- .../generator/DefaultInvoiceGenerator.java | 1 + .../ContiguousIntervalConsumableInArrear.java | 3 + .../TestSubscriptionConsumableInArrear.java | 3 +- 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java index 1323c317ef..c7a909df5c 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java @@ -18,6 +18,7 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.UUID; @@ -27,10 +28,12 @@ import org.killbill.billing.api.TestApiListener.NextEvent; import org.killbill.billing.beatrix.integration.TestIntegrationBase; import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck; +import org.killbill.billing.catalog.api.BillingActionPolicy; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.catalog.api.ProductCategory; import org.killbill.billing.entitlement.api.DefaultEntitlement; import org.killbill.billing.invoice.api.InvoiceItemType; +import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.usage.api.SubscriptionUsageRecord; import org.killbill.billing.usage.api.UnitUsageRecord; import org.killbill.billing.usage.api.UsageRecord; @@ -38,6 +41,8 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableList; + public class TestConsumableInArrear extends TestIntegrationBase { @BeforeMethod(groups = "slow") @@ -46,7 +51,7 @@ public void beforeMethod() throws Exception { } @Test(groups = "slow") - public void testSimple() throws Exception { + public void testWithNoUsageInPeriodAndOldUsage() throws Exception { final AccountData accountData = getAccountData(1); final Account account = createAccountWithNonOsgiPaymentMethod(accountData); @@ -74,6 +79,7 @@ public void testSimple() throws Exception { busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT); clock.setDay(new LocalDate(2012, 5, 1)); + assertListenerStatus(); invoiceChecker.checkInvoice(account.getId(), 2, callContext, @@ -112,6 +118,62 @@ public void testSimple() throws Exception { invoiceChecker.checkInvoice(account.getId(), 4, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")), new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 1), new LocalDate(2012, 8, 1), InvoiceItemType.USAGE, new BigDecimal("11.80"))); + } + + + @Test(groups = "slow") + public void testWithCancellation() throws Exception { + + final AccountData accountData = getAccountData(1); + final Account account = createAccountWithNonOsgiPaymentMethod(accountData); + accountChecker.checkAccount(account.getId(), accountData, callContext); + + // We take april as it has 30 days (easier to play with BCD) + // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC + clock.setDay(new LocalDate(2012, 4, 1)); + + // + // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE + // + final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.INVOICE); + // Check bundle after BP got created otherwise we get an error from auditApi. + subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext); + invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0"))); + + // + // ADD ADD_ON ON THE SAME DAY + // + final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE); + + setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 1), 99L, callContext); + setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext); + + busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT); + clock.setDay(new LocalDate(2012, 5, 1)); + assertListenerStatus(); + invoiceChecker.checkInvoice(account.getId(), 2, callContext, + new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")), + new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90"))); + + + setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 3), 99L, callContext); + setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 5), 100L, callContext); + + // This one should be ignored + setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 29), 100L, callContext); + + clock.setDay(new LocalDate(2012, 5, 28)); + busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE, NextEvent.PAYMENT); + aoSubscription.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(2012, 5, 28), BillingActionPolicy.IMMEDIATE, ImmutableList.of(), callContext); + assertListenerStatus(); + + invoiceChecker.checkInvoice(account.getId(), 3, callContext, + new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 28), InvoiceItemType.USAGE, new BigDecimal("5.90"))); + + busHandler.pushExpectedEvents(); + clock.setDay(new LocalDate(2012, 6, 1)); + + Thread.sleep(1000); } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index 8fdc8bcd1c..2d6e805c15 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -357,6 +357,7 @@ private List processRecurringEvent(final UUID invoiceId, final UUID throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate); } + // STEPH Move all this logic into RecurringInvoiceItemData for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) { final BigDecimal rate = thisEvent.getRecurringPrice(); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java index 5cbb2236b0..5095a1c715 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java @@ -124,6 +124,9 @@ public ContiguousIntervalConsumableInArrear build(final boolean closedInterval) numberOfPeriod++; nextBillCycleDate = bid.getFutureBillingDateFor(numberOfPeriod); } + if (closedInterval && endDate.isAfter(transitionTimes.get(transitionTimes.size() - 1))) { + transitionTimes.add(endDate); + } isBuilt.set(true); return this; } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java index a2d90d936e..1e9e60940a 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java @@ -78,9 +78,10 @@ public void testComputeInArrearUsageInterval() { assertEquals(result.size(), 3); assertEquals(result.get(0).getUsage().getName(), usageName2); - assertEquals(result.get(0).getTransitionTimes().size(), 2); + assertEquals(result.get(0).getTransitionTimes().size(), 3); assertTrue(result.get(0).getTransitionTimes().get(0).compareTo(new LocalDate(2013, 3, 23)) == 0); assertTrue(result.get(0).getTransitionTimes().get(1).compareTo(new LocalDate(2013, 4, 15)) == 0); + assertTrue(result.get(0).getTransitionTimes().get(2).compareTo(new LocalDate(2013, 4, 23)) == 0); assertEquals(result.get(1).getUsage().getName(), usageName1); assertEquals(result.get(1).getTransitionTimes().size(), 4); From f1b2815d8afe32656fc857f22ea5c4b21722bbcc Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 2 Sep 2015 19:26:22 -0700 Subject: [PATCH 053/137] invoice: Fix issue with recurring billing in arrear In a case of early cancellation for instance, we want the cancellation event to trigger the next invoice (even though we are not due for next billing date), and compute a pro-rated invoice for the last period. --- .../billing/invoice/generator/BillingIntervalDetail.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java index 51f284589f..76843fd121 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java @@ -140,8 +140,8 @@ private void calculateInArrearEffectiveEndDate() { } proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay); - // The proposedDate is greater to our endDate => return it - if (endDate != null && endDate.isBefore(proposedDate)) { + // We honor the endDate as long as it does not go beyond our targetDate (by construction this cannot be after the nextProposedDate neither. + if (endDate != null && !endDate.isAfter(targetDate)) { effectiveEndDate = endDate; } else { effectiveEndDate = proposedDate; From 01a165d4916ccd5a1253abc2051c5b96e369b10f Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 2 Sep 2015 20:03:44 -0700 Subject: [PATCH 054/137] invoice: Fix test failure due to commit f1b2815d8afe32656fc857f22ea5c4b21722bbcc --- .../invoice/generator/TestInArrearBillingIntervalDetail.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java index 009ee8c021..ef93815d41 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java @@ -164,7 +164,7 @@ public void testScenarioEndDateBetweenPeriod1() throws Exception { Assert.assertTrue(billingIntervalDetail.hasSomethingToBill()); Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18")); - Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-18")); + Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-20")); Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18")); } From 88f19a5be9c0a2ce45945604c30814b23333d937 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 3 Sep 2015 10:58:39 -0700 Subject: [PATCH 055/137] catalog: Fix missing currency entry in SpyCarAdvanced.xml --- catalog/src/test/resources/SpyCarAdvanced.xml | 4 ++++ profiles/killbill/src/main/resources/SpyCarAdvanced.xml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/catalog/src/test/resources/SpyCarAdvanced.xml b/catalog/src/test/resources/SpyCarAdvanced.xml index be7c2c67cf..da2b95bc97 100644 --- a/catalog/src/test/resources/SpyCarAdvanced.xml +++ b/catalog/src/test/resources/SpyCarAdvanced.xml @@ -611,6 +611,10 @@ GBP 5.95 + + EUR + 6.95 + USD 7.95 diff --git a/profiles/killbill/src/main/resources/SpyCarAdvanced.xml b/profiles/killbill/src/main/resources/SpyCarAdvanced.xml index be7c2c67cf..da2b95bc97 100644 --- a/profiles/killbill/src/main/resources/SpyCarAdvanced.xml +++ b/profiles/killbill/src/main/resources/SpyCarAdvanced.xml @@ -611,6 +611,10 @@ GBP 5.95 + + EUR + 6.95 + USD 7.95 From 9178b21aaa27712897f7e5e32d7a77c7c19d6d6f Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 4 Sep 2015 12:30:48 -0700 Subject: [PATCH 056/137] payment: Fix infinite retry loop in janitor when a PENDING/UNKNWON transaction remains in that state. The fix is to make sure that we go through all our retries and eventually give up. Added a test case to verify that behavior for the PENDING case. --- .../plugin/api/NoOpPaymentPluginApi.java | 13 ++-- .../IncompletePaymentTransactionTask.java | 35 ++++++---- .../DefaultNoOpPaymentProviderPlugin.java | 5 ++ .../killbill/billing/payment/TestJanitor.java | 67 +++++++++++++++++++ .../TestIncompletePaymentTransactionTask.java | 1 - .../provider/MockPaymentProviderPlugin.java | 7 ++ 6 files changed, 111 insertions(+), 17 deletions(-) diff --git a/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java b/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java index bab9f06fb7..40eee4fc06 100644 --- a/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java +++ b/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java @@ -16,13 +16,18 @@ package org.killbill.billing.payment.plugin.api; +import java.util.List; +import java.util.UUID; + public interface NoOpPaymentPluginApi extends PaymentPluginApi { - public void clear(); + void clear(); + + void makeNextPaymentFailWithError(); - public void makeNextPaymentFailWithError(); + void makeNextPaymentFailWithException(); - public void makeNextPaymentFailWithException(); + void makeAllInvoicesFailWithError(boolean failure); - public void makeAllInvoicesFailWithError(boolean failure); + void updatePaymentTransactions(UUID paymentId, List newTransactions); } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java index 8c013d4cd7..94d318b0c2 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java @@ -142,7 +142,7 @@ public void processPaymentEvent(final PaymentInternalEvent event, final Notifica if (!TRANSACTION_STATUSES_TO_CONSIDER.contains(event.getStatus())) { return; } - insertNewNotificationForUnresolvedTransactionIfNeeded(event.getPaymentTransactionId(), 1, event.getUserToken(), event.getSearchKey1(), event.getSearchKey2()); + insertNewNotificationForUnresolvedTransactionIfNeeded(event.getPaymentTransactionId(), 0, event.getUserToken(), event.getSearchKey1(), event.getSearchKey2()); } public boolean updatePaymentAndTransactionIfNeededWithAccountLock(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin, final InternalTenantContext internalTenantContext) { @@ -200,6 +200,14 @@ private boolean updatePaymentAndTransactionInternal(final PaymentModelDao paymen return false; } + // Our status did not change, so we just insert a new notification (attemptNumber will be incremented) + if (transactionStatus == paymentTransaction.getTransactionStatus()) { + log.debug("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}", + payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); + insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId()); + return false; + } + // Recompute new lastSuccessPaymentState. This is important to be able to allow new operations on the state machine (for e.g an AUTH_SUCCESS would now allow a CAPTURE operation) final String lastSuccessPaymentState = paymentStateMachineHelper.isSuccessState(newPaymentState) ? newPaymentState : null; @@ -213,13 +221,9 @@ private boolean updatePaymentAndTransactionInternal(final PaymentModelDao paymen final String gatewayErrorCode = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayErrorCode() : paymentTransaction.getGatewayErrorCode(); final String gatewayError = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayError() : paymentTransaction.getGatewayErrorMsg(); - if (transactionStatus == paymentTransaction.getTransactionStatus()) { - log.debug("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}", - payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); - } else { - log.info("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}", - payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); - } + + log.info("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}", + payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext); paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(), payment.getId(), paymentTransaction.getTransactionType(), newPaymentState, lastSuccessPaymentState, @@ -248,10 +252,10 @@ private PaymentPluginApi getPaymentPluginApi(final PaymentModelDao item, final S } @VisibleForTesting - DateTime getNextNotificationTime(@Nullable final Integer attemptNumber) { + DateTime getNextNotificationTime(final Integer attemptNumber) { final List retries = paymentConfig.getIncompleteTransactionsRetries(); - if (attemptNumber == null || attemptNumber > retries.size()) { + if (attemptNumber > retries.size()) { return null; } final TimeSpan nextDelay = retries.get(attemptNumber - 1); @@ -259,8 +263,15 @@ DateTime getNextNotificationTime(@Nullable final Integer attemptNumber) { } private void insertNewNotificationForUnresolvedTransactionIfNeeded(final UUID paymentTransactionId, @Nullable final Integer attemptNumber, @Nullable final UUID userToken, final Long accountRecordId, final Long tenantRecordId) { - final NotificationEvent key = new JanitorNotificationKey(paymentTransactionId, IncompletePaymentTransactionTask.class.toString(), attemptNumber); - final DateTime notificationTime = getNextNotificationTime(attemptNumber); + // When we come from a GET path, we don't want to insert a new notification + if (attemptNumber == null) { + return; + } + + // Increment value before we insert + final Integer newAttemptNumber = attemptNumber.intValue() + 1; + final NotificationEvent key = new JanitorNotificationKey(paymentTransactionId, IncompletePaymentTransactionTask.class.toString(), newAttemptNumber); + final DateTime notificationTime = getNextNotificationTime(newAttemptNumber); // Will be null in the GET path or when we run out opf attempts.. if (notificationTime != null) { try { diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java index 2908799550..a1943004cd 100644 --- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java +++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java @@ -92,6 +92,11 @@ public void makeAllInvoicesFailWithError(final boolean failure) { makeAllInvoicesFailWithError.set(failure); } + @Override + public void updatePaymentTransactions(final UUID paymentId, final List newTransactions) { + throw new IllegalStateException("Not implemented"); + } + @Override public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java index 1753af81ed..e99ccaa9b4 100644 --- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java +++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java @@ -47,6 +47,10 @@ import org.killbill.billing.payment.dao.PaymentTransactionModelDao; import org.killbill.billing.payment.glue.DefaultPaymentService; import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi; +import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; +import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; +import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin; import org.killbill.billing.payment.provider.MockPaymentProviderPlugin; import org.killbill.billing.platform.api.KillbillConfigSource; import org.killbill.billing.util.callcontext.InternalCallContextFactory; @@ -55,6 +59,7 @@ import org.killbill.notificationq.api.NotificationEventWithMetadata; import org.killbill.notificationq.api.NotificationQueueService; import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue; +import org.skife.config.TimeSpan; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.tweak.HandleCallback; import org.testng.Assert; @@ -393,6 +398,58 @@ public void testPendingEntries() throws PaymentApiException, EventBusException, Assert.assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS); } + // The test will check that when a PENDING entry stays PENDING, we go through all our retries and evebtually give up (no infinite loop of retries) + @Test(groups = "slow") + public void testPendingEntriesThatDontMove() throws PaymentApiException, EventBusException, NoSuchNotificationQueue, PaymentPluginApiException, InterruptedException { + + final BigDecimal requestedAmount = BigDecimal.TEN; + final String paymentExternalKey = "haha"; + final String transactionExternalKey = "hoho!"; + + testListener.pushExpectedEvent(NextEvent.PAYMENT); + final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey, + transactionExternalKey, ImmutableList.of(), callContext); + testListener.assertListenerStatus(); + + final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext); + + // Artificially move the transaction status to PENDING AND update state on the plugin as well + final List paymentTransactions = mockPaymentProviderPlugin.getPaymentInfo(account.getId(), payment.getId(), ImmutableList.of(), callContext); + final PaymentTransactionInfoPlugin oTx = paymentTransactions.remove(0); + final PaymentTransactionInfoPlugin updatePaymentTransaction = new DefaultNoOpPaymentInfoPlugin(oTx.getKbPaymentId(), oTx.getKbTransactionPaymentId(), oTx.getTransactionType(), oTx.getAmount(), oTx.getCurrency(), oTx.getCreatedDate(), oTx.getCreatedDate(), PaymentPluginStatus.PENDING, null); + paymentTransactions.add(updatePaymentTransaction); + mockPaymentProviderPlugin.updatePaymentTransactions(payment.getId(), paymentTransactions); + + final String paymentStateName = paymentSMHelper.getPendingStateForTransaction(TransactionType.AUTHORIZE).toString(); + + + testListener.pushExpectedEvent(NextEvent.PAYMENT); + paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), payment.getId(), TransactionType.AUTHORIZE, paymentStateName, paymentStateName, + payment.getTransactions().get(0).getId(), TransactionStatus.PENDING, requestedAmount, account.getCurrency(), + "loup", "chat", internalCallContext); + testListener.assertListenerStatus(); + + // 15s,1m,3m,1h,1d,1d,1d,1d,1d + for (TimeSpan cur : paymentConfig.getIncompleteTransactionsRetries()) { + // Verify there is a notification to retry updating the value + assertEquals(getPendingNotificationCnt(internalCallContext), 1); + + clock.addDeltaFromReality(cur.getMillis() + 1); + + // We add a sleep here to make sure the notification gets processed. Note that calling assertNotificationsCompleted would not work + // because there is a point in time where the notification queue is empty (showing notification was processed), but the processing of the notification + // will itself enter a new notification, and so the synchronization is difficult without writing *too much code*. + Thread.sleep(1000); + + //assertNotificationsCompleted(internalCallContext, 5); + final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, ImmutableList.of(), callContext); + Assert.assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING); + } + + assertEquals(getPendingNotificationCnt(internalCallContext), 0); + } + + private List createPropertiesForInvoice(final Invoice invoice) { final List result = new ArrayList(); result.add(new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false)); @@ -441,5 +498,15 @@ public Boolean call() throws Exception { fail("Test failed ", e); } } + + private int getPendingNotificationCnt(final InternalCallContext internalCallContext) { + try { + return notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()).size(); + } catch (final Exception e) { + fail("Test failed ", e); + } + // Not reached.. + return -1; + } } diff --git a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java index 569070f1a9..9e81218a2a 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java @@ -35,7 +35,6 @@ public class TestIncompletePaymentTransactionTask extends PaymentTestSuiteNoDB { @Test(groups = "fast") public void testGetNextNotificationTime() { - assertNull(incompletePaymentTransactionTask.getNextNotificationTime(null)); final DateTime initTime = clock.getUTCNow(); // Based on config "15s,1m,3m,1h,1d,1d,1d,1d,1d" diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java index 9e8243d662..f3b7088f8f 100644 --- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java +++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java @@ -208,6 +208,13 @@ public void makeAllInvoicesFailWithError(final boolean failure) { makeAllInvoicesFailWithError.set(failure); } + @Override + public void updatePaymentTransactions(final UUID paymentId, final List newTransactions) { + if (paymentTransactions.containsKey(paymentId.toString())) { + paymentTransactions.put (paymentId.toString(), newTransactions); + } + } + @Override public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { From f72e1dbff6d2cb6e7bcd1657e50deac7a6697624 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 4 Sep 2015 13:35:32 -0700 Subject: [PATCH 057/137] invoice, payment: Export a new system property org.killbill.(payment|invoice).globalLock.retries to configure max # retries for global lock (Tedious change that reqiures a lot of refactoring for all the non guice injected classes) --- .../billing/invoice/InvoiceDispatcher.java | 5 +--- .../billing/invoice/api/InvoiceApiHelper.java | 10 +++---- .../TestDefaultInvoiceGenerator.java | 5 ++++ .../payment/core/PaymentMethodProcessor.java | 12 +++++--- .../billing/payment/core/ProcessorBase.java | 28 ++++++++++-------- .../core/janitor/CompletionTaskBase.java | 2 +- .../core/sm/OperationCallbackBase.java | 9 ++++-- .../core/sm/PaymentAutomatonRunner.java | 17 ++++++----- .../PluginControlPaymentAutomatonRunner.java | 18 +++++++----- .../sm/control/AuthorizeControlOperation.java | 10 +++++-- .../sm/control/CaptureControlOperation.java | 10 +++++-- .../control/ChargebackControlOperation.java | 10 +++++-- .../control/CompletionControlOperation.java | 13 +++++---- .../sm/control/CreditControlOperation.java | 10 +++++-- .../sm/control/OperationControlCallback.java | 8 +++-- .../sm/control/PurchaseControlOperation.java | 10 +++++-- .../sm/control/RefundControlOperation.java | 10 +++++-- .../core/sm/control/VoidControlOperation.java | 10 +++++-- .../core/sm/payments/AuthorizeOperation.java | 7 +++-- .../core/sm/payments/CaptureOperation.java | 7 +++-- .../core/sm/payments/ChargebackOperation.java | 7 +++-- .../core/sm/payments/CreditOperation.java | 7 +++-- .../core/sm/payments/PaymentOperation.java | 8 +++-- .../core/sm/payments/PurchaseOperation.java | 7 +++-- .../core/sm/payments/RefundOperation.java | 7 +++-- .../core/sm/payments/VoidOperation.java | 7 +++-- .../MockRetryAuthorizeOperationCallback.java | 12 ++++++-- .../payment/core/sm/TestPaymentOperation.java | 9 ++++-- .../payment/core/sm/TestPluginOperation.java | 12 ++++---- .../payment/core/sm/TestRetryablePayment.java | 1 + .../billing/util/config/InvoiceConfig.java | 13 ++++++--- .../billing/util/config/PaymentConfig.java | 29 +++++++++++-------- 32 files changed, 219 insertions(+), 111 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java index ddfa83ed35..2b6fd8241e 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java @@ -115,9 +115,6 @@ public class InvoiceDispatcher { private static final Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class); - // 50 * 100ms = 5sec - private static final int NB_LOCK_TRY = 50; - private static final Ordering UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural(); private static final NullDryRunArguments NULL_DRY_RUN_ARGUMENTS = new NullDryRunArguments(); @@ -210,7 +207,7 @@ public Invoice processAccount(final UUID accountId, final DateTime targetDate, @Nullable final DryRunArguments dryRunArguments, final InternalCallContext context) throws InvoiceApiException { GlobalLock lock = null; try { - lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), NB_LOCK_TRY); + lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), invoiceConfig.getMaxGlobalLockRetries()); return processAccountWithLock(accountId, targetDate, dryRunArguments, context); } catch (final LockFailedException e) { diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java index b56af28664..30b968e2a0 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java @@ -39,6 +39,7 @@ import org.killbill.billing.util.UUIDs; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.InternalCallContextFactory; +import org.killbill.billing.util.config.InvoiceConfig; import org.killbill.billing.util.globallocker.LockerType; import org.killbill.commons.locker.GlobalLock; import org.killbill.commons.locker.GlobalLocker; @@ -57,26 +58,25 @@ public class InvoiceApiHelper { private static final Logger log = LoggerFactory.getLogger(InvoiceApiHelper.class); - // 50 * 100ms = 5sec - private static final int NB_LOCK_TRY = 50; - private final InvoicePluginDispatcher invoicePluginDispatcher; private final InvoiceDao dao; private final GlobalLocker locker; private final InternalCallContextFactory internalCallContextFactory; + private final InvoiceConfig invoiceConfig; @Inject - public InvoiceApiHelper(final InvoicePluginDispatcher invoicePluginDispatcher, final InvoiceDao dao, final GlobalLocker locker, final InternalCallContextFactory internalCallContextFactory) { + public InvoiceApiHelper(final InvoicePluginDispatcher invoicePluginDispatcher, final InvoiceDao dao, final GlobalLocker locker, final InvoiceConfig invoiceConfig, final InternalCallContextFactory internalCallContextFactory) { this.invoicePluginDispatcher = invoicePluginDispatcher; this.dao = dao; this.locker = locker; + this.invoiceConfig = invoiceConfig; this.internalCallContextFactory = internalCallContextFactory; } public List dispatchToInvoicePluginsAndInsertItems(final UUID accountId, final WithAccountLock withAccountLock, final CallContext context) throws InvoiceApiException { GlobalLock lock = null; try { - lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), NB_LOCK_TRY); + lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), invoiceConfig.getMaxGlobalLockRetries()); final Iterable invoicesForPlugins = withAccountLock.prepareInvoices(); diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java index 9094ddcb4d..4817b83ca4 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java @@ -127,6 +127,11 @@ public TimeSpan getDryRunNotificationSchedule() { public int getMaxRawUsagePreviousPeriod() { return -1; } + + @Override + public int getMaxGlobalLockRetries() { + return 10; + } }; this.generator = new DefaultInvoiceGenerator(clock, invoiceConfig, null); this.account = new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8)) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java index 102c3d2d31..6368518e3f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java @@ -79,6 +79,8 @@ public class PaymentMethodProcessor extends ProcessorBase { private final PluginDispatcher uuidPluginNotificationDispatcher; + private final PaymentConfig paymentConfig; + @Inject public PaymentMethodProcessor(final OSGIServiceRegistration pluginRegistry, final AccountInternalApi accountInternalApi, @@ -92,6 +94,7 @@ public PaymentMethodProcessor(final OSGIServiceRegistration pl final Clock clock) { super(pluginRegistry, accountInternalApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock); final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit()); + this.paymentConfig = paymentConfig; this.uuidPluginNotificationDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executors); } @@ -102,7 +105,8 @@ public UUID addPaymentMethod(final String paymentMethodExternalKey, final String return dispatchWithExceptionHandling(account, new CallableWithAccountLock(locker, account.getExternalKey(), - new WithAccountLockCallback, PaymentApiException>() { + paymentConfig, + new DispatcherCallback, PaymentApiException>() { @Override public PluginDispatcherReturnType doOperation() throws PaymentApiException { @@ -331,7 +335,7 @@ public void deletedPaymentMethod(final Account account, final UUID paymentMethod final Iterable properties, final CallContext callContext, final InternalCallContext context) throws PaymentApiException { try { - new WithAccountLock().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback, PaymentApiException>() { + new WithAccountLock(paymentConfig).processAccountWithLock(locker, account.getExternalKey(), new DispatcherCallback, PaymentApiException>() { @Override public PluginDispatcherReturnType doOperation() throws PaymentApiException { @@ -374,7 +378,7 @@ public PluginDispatcherReturnType doOperation() throws PaymentApiException public void setDefaultPaymentMethod(final Account account, final UUID paymentMethodId, final Iterable properties, final CallContext callContext, final InternalCallContext context) throws PaymentApiException { try { - new WithAccountLock().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback, PaymentApiException>() { + new WithAccountLock(paymentConfig).processAccountWithLock(locker, account.getExternalKey(), new DispatcherCallback, PaymentApiException>() { @Override public PluginDispatcherReturnType doOperation() throws PaymentApiException { @@ -438,7 +442,7 @@ public List refreshPaymentMethods(final String pluginName, final } try { - final PluginDispatcherReturnType> result = new WithAccountLock, PaymentApiException>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback>, PaymentApiException>() { + final PluginDispatcherReturnType> result = new WithAccountLock, PaymentApiException>(paymentConfig).processAccountWithLock(locker, account.getExternalKey(), new DispatcherCallback>, PaymentApiException>() { @Override public PluginDispatcherReturnType> doOperation() throws PaymentApiException { diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java index 3b76d67891..1e8be54331 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java @@ -23,7 +23,6 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeoutException; import javax.annotation.Nullable; @@ -47,6 +46,7 @@ import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.InternalCallContextFactory; import org.killbill.billing.util.callcontext.TenantContext; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.billing.util.globallocker.LockerType; import org.killbill.billing.util.tag.ControlTagType; import org.killbill.billing.util.tag.Tag; @@ -63,9 +63,6 @@ public abstract class ProcessorBase { - // 50 * 100ms = 5sec - public static final int NB_LOCK_TRY = 50; - protected final OSGIServiceRegistration pluginRegistry; protected final AccountInternalApi accountInternalApi; protected final GlobalLocker locker; @@ -158,9 +155,7 @@ protected CallContext buildCallContext(final InternalCallContext context) { return internalCallContextFactory.createCallContext(context); } - // TODO Rename - there is no lock! - public interface WithAccountLockCallback { - + public interface DispatcherCallback { public PluginDispatcherReturnType doOperation() throws ExceptionType; } @@ -168,29 +163,38 @@ public static class CallableWithAccountLock, ExceptionType> callback; + private final DispatcherCallback, ExceptionType> callback; + private final PaymentConfig paymentConfig; public CallableWithAccountLock(final GlobalLocker locker, final String accountExternalKey, - final WithAccountLockCallback, ExceptionType> callback) { + final PaymentConfig paymentConfig, + final DispatcherCallback, ExceptionType> callback) { this.locker = locker; this.accountExternalKey = accountExternalKey; this.callback = callback; + this.paymentConfig = paymentConfig; } @Override public PluginDispatcherReturnType call() throws ExceptionType, LockFailedException { - return new WithAccountLock().processAccountWithLock(locker, accountExternalKey, callback); + return new WithAccountLock(paymentConfig).processAccountWithLock(locker, accountExternalKey, callback); } } public static class WithAccountLock { - public PluginDispatcherReturnType processAccountWithLock(final GlobalLocker locker, final String accountExternalKey, final WithAccountLockCallback, ExceptionType> callback) + private final PaymentConfig paymentConfig; + + public WithAccountLock(final PaymentConfig paymentConfig) { + this.paymentConfig = paymentConfig; + } + + public PluginDispatcherReturnType processAccountWithLock(final GlobalLocker locker, final String accountExternalKey, final DispatcherCallback, ExceptionType> callback) throws ExceptionType, LockFailedException { GlobalLock lock = null; try { - lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountExternalKey, NB_LOCK_TRY); + lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountExternalKey, paymentConfig.getMaxGlobalLockRetries()); return callback.doOperation(); } finally { if (lock != null) { diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java index 60281b49e0..2d4a157705 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java @@ -124,7 +124,7 @@ protected T doJanitorOperationWithAccountLock(final JanitorIterationCallback GlobalLock lock = null; try { final Account account = accountInternalApi.getAccountByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext); - lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), account.getExternalKey(), ProcessorBase.NB_LOCK_TRY); + lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), account.getExternalKey(), paymentConfig.getMaxGlobalLockRetries()); return callback.doIteration(); } catch (AccountApiException e) { log.warn(String.format("Janitor failed to retrieve account with recordId %s", internalTenantContext.getAccountRecordId()), e); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java index 8ab58ff559..34c8c1b403 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java @@ -25,9 +25,10 @@ import org.killbill.automaton.OperationResult; import org.killbill.billing.account.api.Account; import org.killbill.billing.payment.core.ProcessorBase.CallableWithAccountLock; -import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback; +import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,15 +39,18 @@ public abstract class OperationCallbackBase paymentPluginDispatcher; + private final PaymentConfig paymentConfig; protected final PaymentStateContext paymentStateContext; protected OperationCallbackBase(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) { this.locker = locker; this.paymentPluginDispatcher = paymentPluginDispatcher; this.paymentStateContext = paymentStateContext; + this.paymentConfig = paymentConfig; } // @@ -54,13 +58,14 @@ protected OperationCallbackBase(final GlobalLocker locker, // The dispatcher may throw a TimeoutException, ExecutionException, or InterruptedException; those will be handled in specific // callback to eventually throw a OperationException, that will be used to drive the state machine in the right direction. // - protected OperationResult dispatchWithAccountLockAndTimeout(final WithAccountLockCallback, ExceptionType> callback) throws OperationException { + protected OperationResult dispatchWithAccountLockAndTimeout(final DispatcherCallback, ExceptionType> callback) throws OperationException { final Account account = paymentStateContext.getAccount(); logger.debug("Dispatching plugin call for account {}", account.getExternalKey()); try { final Callable> task = new CallableWithAccountLock(locker, account.getExternalKey(), + paymentConfig, callback); final OperationResult operationResult = paymentPluginDispatcher.dispatchWithTimeout(task); logger.debug("Successful plugin call for account {} with result {}", account.getExternalKey(), operationResult); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java index d0f56a03c7..49702f5d15 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java @@ -90,6 +90,7 @@ public class PaymentAutomatonRunner { protected final OSGIServiceRegistration pluginRegistry; protected final Clock clock; private final PersistentBus eventBus; + private final PaymentConfig paymentConfig; @Inject public PaymentAutomatonRunner(final PaymentConfig paymentConfig, @@ -106,7 +107,7 @@ public PaymentAutomatonRunner(final PaymentConfig paymentConfig, this.pluginRegistry = pluginRegistry; this.clock = clock; this.eventBus = eventBus; - + this.paymentConfig = paymentConfig; final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit()); this.paymentPluginDispatcher = new PluginDispatcher(paymentPluginTimeoutSec, executors); @@ -151,37 +152,37 @@ public UUID run(final boolean isApiPayment, final TransactionType transactionTyp final EnteringStateCallback enteringStateCallback; switch (transactionType) { case PURCHASE: - operationCallback = new PurchaseOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + operationCallback = new PurchaseOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); leavingStateCallback = new PurchaseInitiated(daoHelper, paymentStateContext); enteringStateCallback = new PurchaseCompleted(daoHelper, paymentStateContext); break; case AUTHORIZE: - operationCallback = new AuthorizeOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + operationCallback = new AuthorizeOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); leavingStateCallback = new AuthorizeInitiated(daoHelper, paymentStateContext); enteringStateCallback = new AuthorizeCompleted(daoHelper, paymentStateContext); break; case CAPTURE: - operationCallback = new CaptureOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + operationCallback = new CaptureOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); leavingStateCallback = new CaptureInitiated(daoHelper, paymentStateContext); enteringStateCallback = new CaptureCompleted(daoHelper, paymentStateContext); break; case VOID: - operationCallback = new VoidOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + operationCallback = new VoidOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); leavingStateCallback = new VoidInitiated(daoHelper, paymentStateContext); enteringStateCallback = new VoidCompleted(daoHelper, paymentStateContext); break; case REFUND: - operationCallback = new RefundOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + operationCallback = new RefundOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); leavingStateCallback = new RefundInitiated(daoHelper, paymentStateContext); enteringStateCallback = new RefundCompleted(daoHelper, paymentStateContext); break; case CREDIT: - operationCallback = new CreditOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + operationCallback = new CreditOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); leavingStateCallback = new CreditInitiated(daoHelper, paymentStateContext); enteringStateCallback = new CreditCompleted(daoHelper, paymentStateContext); break; case CHARGEBACK: - operationCallback = new ChargebackOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + operationCallback = new ChargebackOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); leavingStateCallback = new ChargebackInitiated(daoHelper, paymentStateContext); enteringStateCallback = new ChargebackCompleted(daoHelper, paymentStateContext); break; diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java index 4f58817507..b15dcdbbfe 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java @@ -77,6 +77,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner private final RetryServiceScheduler retryServiceScheduler; private final PaymentControlStateMachineHelper paymentControlStateMachineHelper; private final ControlPluginRunner controlPluginRunner; + private final PaymentConfig paymentConfig; @Inject public PluginControlPaymentAutomatonRunner(final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration pluginRegistry, @@ -89,6 +90,7 @@ public PluginControlPaymentAutomatonRunner(final PaymentDao paymentDao, final Gl this.retryServiceScheduler = retryServiceScheduler; this.paymentControlStateMachineHelper = paymentControlStateMachineHelper; this.controlPluginRunner = controlPluginRunner; + this.paymentConfig = paymentConfig; } public Payment run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId, @@ -133,7 +135,7 @@ public Payment run(final State state, final boolean isApiPayment, final Transact public Payment completeRun(final PaymentStateControlContext paymentStateContext) throws PaymentApiException { try { - final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); final LeavingStateCallback leavingStateCallback = new NoopControlInitiated(); final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler); @@ -166,25 +168,25 @@ OperationCallback createOperationCallback(final TransactionType transactionType, final OperationCallback callback; switch (transactionType) { case AUTHORIZE: - callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); break; case CAPTURE: - callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); break; case PURCHASE: - callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); break; case VOID: - callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); break; case CREDIT: - callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); break; case REFUND: - callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); break; case CHARGEBACK: - callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); break; default: throw new IllegalStateException("Unsupported transaction type " + transactionType); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java index 0459ad2e5a..4f05f5e787 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java @@ -23,12 +23,18 @@ import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; public class AuthorizeControlOperation extends OperationControlCallback { - public AuthorizeControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + public AuthorizeControlOperation(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java index 2b76885521..54f133f039 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java @@ -23,12 +23,18 @@ import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; public class CaptureControlOperation extends OperationControlCallback { - public CaptureControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + public CaptureControlOperation(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java index 07b53442fe..4836a2bb86 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java @@ -24,12 +24,18 @@ import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; public class ChargebackControlOperation extends OperationControlCallback { - public ChargebackControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + public ChargebackControlOperation(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java index 6f3e300244..d7cdac926f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java @@ -20,17 +20,16 @@ import org.killbill.automaton.OperationException; import org.killbill.automaton.OperationResult; import org.killbill.billing.control.plugin.api.PaymentApiType; -import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.Payment; import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.core.PaymentProcessor; -import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback; +import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback; import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext; import org.killbill.billing.payment.dao.PaymentTransactionModelDao; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; import org.killbill.billing.control.plugin.api.PaymentControlContext; -import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; // @@ -38,17 +37,19 @@ // public class CompletionControlOperation extends OperationControlCallback { - public CompletionControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + public CompletionControlOperation(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner); } @Override public OperationResult doOperationCallback() throws OperationException { - return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback, OperationException>() { + return dispatchWithAccountLockAndTimeout(new DispatcherCallback, OperationException>() { @Override public PluginDispatcherReturnType doOperation() throws OperationException { final PaymentTransactionModelDao transaction = paymentStateContext.getPaymentTransactionModelDao(); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java index 1a071fa549..5d2c6433d1 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java @@ -23,12 +23,18 @@ import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; public class CreditControlOperation extends OperationControlCallback { - public CreditControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + public CreditControlOperation(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java index a1e89282fe..add2adf747 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java @@ -39,12 +39,13 @@ import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionStatus; import org.killbill.billing.payment.core.PaymentProcessor; -import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback; +import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback; import org.killbill.billing.payment.core.sm.OperationCallbackBase; import org.killbill.billing.payment.core.sm.PaymentStateContext; import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.killbill.commons.locker.LockFailedException; import org.slf4j.Logger; @@ -64,8 +65,9 @@ protected OperationControlCallback(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, + final PaymentConfig paymentConfig, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext); + super(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); this.paymentProcessor = paymentProcessor; this.controlPluginRunner = controlPluginRunner; this.paymentStateControlContext = paymentStateContext; @@ -77,7 +79,7 @@ protected OperationControlCallback(final GlobalLocker locker, @Override public OperationResult doOperationCallback() throws OperationException { - return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback, OperationException>() { + return dispatchWithAccountLockAndTimeout(new DispatcherCallback, OperationException>() { @Override public PluginDispatcherReturnType doOperation() throws OperationException { diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java index 7ad848fa1d..4a766c719a 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java @@ -23,12 +23,18 @@ import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; public class PurchaseControlOperation extends OperationControlCallback { - public PurchaseControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + public PurchaseControlOperation(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java index 5f4d7cde26..4c60dd372f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java @@ -23,12 +23,18 @@ import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; public class RefundControlOperation extends OperationControlCallback { - public RefundControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + public RefundControlOperation(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java index 00200d1c11..db74fa9fdb 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java @@ -23,12 +23,18 @@ import org.killbill.billing.payment.core.PaymentProcessor; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; public class VoidControlOperation extends OperationControlCallback { - public VoidControlOperation(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + public VoidControlOperation(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner) { + super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java index 291908913a..d8eb4056a1 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java @@ -24,6 +24,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,9 +34,11 @@ public class AuthorizeOperation extends PaymentOperation { private final Logger logger = LoggerFactory.getLogger(AuthorizeOperation.class); public AuthorizeOperation(final PaymentAutomatonDAOHelper daoHelper, - final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java index 43903b8339..e6b67c5bd1 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java @@ -24,6 +24,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,9 +34,11 @@ public class CaptureOperation extends PaymentOperation { private final Logger logger = LoggerFactory.getLogger(CaptureOperation.class); public CaptureOperation(final PaymentAutomatonDAOHelper daoHelper, - final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java index 0585c0fe49..7193ab1e65 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java @@ -30,6 +30,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,9 +40,11 @@ public class ChargebackOperation extends PaymentOperation { private final Logger logger = LoggerFactory.getLogger(ChargebackOperation.class); public ChargebackOperation(final PaymentAutomatonDAOHelper daoHelper, - final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java index c206625dd8..53b5634244 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java @@ -24,6 +24,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,9 +34,11 @@ public class CreditOperation extends PaymentOperation { private final Logger logger = LoggerFactory.getLogger(CreditOperation.class); public CreditOperation(final PaymentAutomatonDAOHelper daoHelper, - final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java index ba6e48dfac..489640938f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java @@ -30,7 +30,7 @@ import org.killbill.billing.payment.api.TransactionStatus; import org.killbill.billing.payment.api.TransactionType; import org.killbill.billing.payment.core.PaymentTransactionInfoPluginConverter; -import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback; +import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback; import org.killbill.billing.payment.core.sm.OperationCallbackBase; import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper; import org.killbill.billing.payment.core.sm.PaymentStateContext; @@ -42,6 +42,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.killbill.commons.locker.LockFailedException; @@ -59,8 +60,9 @@ public abstract class PaymentOperation extends OperationCallbackBase paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) { - super(locker, paymentPluginDispatcher, paymentStateContext); + super(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); this.daoHelper = daoHelper; } @@ -154,7 +156,7 @@ protected BigDecimal getSumAmount(final Iterable tra } private OperationResult doOperationCallbackWithDispatchAndAccountLock() throws OperationException { - return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback, OperationException>() { + return dispatchWithAccountLockAndTimeout(new DispatcherCallback, OperationException>() { @Override public PluginDispatcherReturnType doOperation() throws OperationException { final OperationResult result = doSimpleOperationCallback(); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java index 7c9aabbec5..1ddf4b1a92 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java @@ -24,6 +24,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,9 +34,11 @@ public class PurchaseOperation extends PaymentOperation { private final Logger logger = LoggerFactory.getLogger(PurchaseOperation.class); public PurchaseOperation(final PaymentAutomatonDAOHelper daoHelper, - final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java index 77534c59a8..22d923988e 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java @@ -24,6 +24,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,9 +34,11 @@ public class RefundOperation extends PaymentOperation { private final Logger logger = LoggerFactory.getLogger(RefundOperation.class); public RefundOperation(final PaymentAutomatonDAOHelper daoHelper, - final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java index a188a434bb..0d776b6e0a 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java @@ -24,6 +24,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,9 +34,11 @@ public class VoidOperation extends PaymentOperation { private final Logger logger = LoggerFactory.getLogger(VoidOperation.class); public VoidOperation(final PaymentAutomatonDAOHelper daoHelper, - final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, + final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); } @Override diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java index 3dd00a4caf..1a8f812d44 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java @@ -35,6 +35,7 @@ import org.killbill.billing.payment.dao.PaymentTransactionModelDao; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.clock.Clock; import org.killbill.commons.locker.GlobalLocker; @@ -46,8 +47,15 @@ public class MockRetryAuthorizeOperationCallback extends AuthorizeControlOperati private Exception exception; private OperationResult result; - public MockRetryAuthorizeOperationCallback(final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner, final PaymentDao paymentDao, final Clock clock) { - super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner); + public MockRetryAuthorizeOperationCallback(final GlobalLocker locker, + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateControlContext paymentStateContext, + final PaymentProcessor paymentProcessor, + final ControlPluginRunner controlPluginRunner, + final PaymentDao paymentDao, + final Clock clock) { + super(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner); this.paymentDao = paymentDao; this.clock = clock; } diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java index 7e4e1b63c9..1765befe65 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java @@ -38,6 +38,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; import org.killbill.billing.payment.provider.MockPaymentProviderPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.killbill.commons.locker.memory.MemoryGlobalLocker; import org.mockito.Mockito; @@ -125,7 +126,7 @@ private void setUp(final PaymentPluginStatus paymentPluginStatus) throws Excepti Mockito.when(paymentDao.getPaymentMethodIncludedDeleted(paymentStateContext.getPaymentMethodId(), internalCallContext)).thenReturn(paymentMethodModelDao); final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, clock.getUTCNow(), paymentDao, registry, internalCallContext, eventBus, paymentSMHelper); - paymentOperation = new PaymentOperationTest(paymentPluginStatus, daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + paymentOperation = new PaymentOperationTest(paymentPluginStatus, daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); } private static final class PaymentOperationTest extends PaymentOperation { @@ -134,8 +135,10 @@ private static final class PaymentOperationTest extends PaymentOperation { public PaymentOperationTest(@Nullable final PaymentPluginStatus paymentPluginStatus, final PaymentAutomatonDAOHelper daoHelper, final GlobalLocker locker, - final PluginDispatcher paymentPluginDispatcher, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + final PluginDispatcher paymentPluginDispatcher, + final PaymentConfig paymentConfig, + final PaymentStateContext paymentStateContext) throws PaymentApiException { + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); this.paymentInfoPlugin = (paymentPluginStatus == null ? null : getPaymentInfoPlugin(paymentPluginStatus)); } diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java index 9c73489543..75e39c3729 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -36,12 +35,13 @@ import org.killbill.billing.payment.api.PaymentApiException; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.api.TransactionType; -import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback; +import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback; import org.killbill.billing.payment.core.sm.payments.PaymentOperation; import org.killbill.billing.payment.dispatcher.PluginDispatcher; import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; +import org.killbill.billing.util.config.PaymentConfig; import org.killbill.commons.locker.GlobalLocker; import org.killbill.commons.locker.memory.MemoryGlobalLocker; import org.mockito.Mockito; @@ -202,10 +202,10 @@ private PaymentOperation getPluginOperation(final boolean shouldLockAccount, fin final PaymentAutomatonDAOHelper daoHelper = Mockito.mock(PaymentAutomatonDAOHelper.class); Mockito.when(daoHelper.getPaymentProviderPlugin()).thenReturn(null); - return new PluginOperationTest(daoHelper, locker, paymentPluginDispatcher, paymentStateContext); + return new PluginOperationTest(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext); } - private static final class CallbackTest implements WithAccountLockCallback, PaymentApiException> { + private static final class CallbackTest implements DispatcherCallback, PaymentApiException> { private final AtomicInteger runCount = new AtomicInteger(0); @@ -274,8 +274,8 @@ public int getRunCount() { private static final class PluginOperationTest extends PaymentOperation { - protected PluginOperationTest(final PaymentAutomatonDAOHelper daoHelper, final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentStateContext paymentStateContext) throws PaymentApiException { - super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext); + protected PluginOperationTest(final PaymentAutomatonDAOHelper daoHelper, final GlobalLocker locker, final PluginDispatcher paymentPluginDispatcher, final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException { + super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext); } @Override diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java index b79660746d..a64c15a567 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java @@ -189,6 +189,7 @@ public void beforeMethod() throws Exception { mockRetryAuthorizeOperationCallback = new MockRetryAuthorizeOperationCallback(locker, runner.getPaymentPluginDispatcher(), + paymentConfig, paymentStateContext, null, controlPluginRunner, diff --git a/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java b/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java index f42a635fed..19b3368cb9 100644 --- a/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java +++ b/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java @@ -26,22 +26,27 @@ public interface InvoiceConfig extends KillbillConfig { @Config("org.killbill.invoice.maxNumberOfMonthsInFuture") @Default("36") @Description("Maximum target date to consider when generating an invoice") - public int getNumberOfMonthsInFuture(); + int getNumberOfMonthsInFuture(); @Config("org.killbill.invoice.emailNotificationsEnabled") @Default("false") @Description("Whether to send email notifications on invoice creation (for configured accounts)") - public boolean isEmailNotificationsEnabled(); + boolean isEmailNotificationsEnabled(); @Config("org.killbill.invoice.dryRunNotificationSchedule") @Default("0s") @Description("DryRun invoice notification time before targetDate (ignored if set to 0s)") - public TimeSpan getDryRunNotificationSchedule(); + TimeSpan getDryRunNotificationSchedule(); @Config("org.killbill.invoice.readMaxRawUsagePreviousPeriod") @Default("2") @Description("Maximum number of past billing periods we use to fetch raw usage data (usage optimization)") - public int getMaxRawUsagePreviousPeriod(); + int getMaxRawUsagePreviousPeriod(); + + @Config("org.killbill.invoice.globalLock.retries") + @Default("50") + @Description("Maximum number of times the system will retry to grab global lock (with a 100ms wait each time)") + int getMaxGlobalLockRetries(); } diff --git a/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java b/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java index 7b630c615e..9eadf6030b 100644 --- a/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java +++ b/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java @@ -29,60 +29,65 @@ public interface PaymentConfig extends KillbillConfig { // See ExternalPaymentProviderPlugin.PLUGIN_NAME @Default("__external_payment__") @Description("Default payment provider to use") - public String getDefaultPaymentProvider(); + String getDefaultPaymentProvider(); @Config("org.killbill.payment.retry.days") @Default("8,8,8") @Description("Specify the number of payment retries along with the interval in days between payment retries when payment failures occur") - public List getPaymentFailureRetryDays(); + List getPaymentFailureRetryDays(); @Config("org.killbill.payment.failure.retry.start.sec") @Default("300") @Description("Specify the interval of time in seconds before retrying a payment that failed due to a plugin failure (gateway is down, transient error, ...") - public int getPluginFailureInitialRetryInSec(); + int getPluginFailureInitialRetryInSec(); @Config("org.killbill.payment.failure.retry.multiplier") @Default("2") @Description("Specify the multiplier to apply between in retry before retrying a payment that failed due to a plugin failure (gateway is down, transient error, ...") - public int getPluginFailureRetryMultiplier(); + int getPluginFailureRetryMultiplier(); @Config("org.killbill.payment.failure.retry.max.attempts") @Default("8") @Description("Specify the max number of attempts before retrying a payment that failed due to a plugin failure (gateway is down, transient error, ...\"") - public int getPluginFailureRetryMaxAttempts(); + int getPluginFailureRetryMaxAttempts(); @Config("org.killbill.payment.plugin.timeout") @Default("30s") @Description("Timeout for each payment attempt") - public TimeSpan getPaymentPluginTimeout(); + TimeSpan getPaymentPluginTimeout(); @Config("org.killbill.payment.plugin.threads.nb") @Default("10") @Description("Number of threads for plugin executor dispatcher") - public int getPaymentPluginThreadNb(); + int getPaymentPluginThreadNb(); @Config("org.killbill.payment.janitor.attempts.delay") @Default("12h") @Description("Delay before which unresolved attempt should be retried") - public TimeSpan getIncompleteAttemptsTimeSpanDelay(); + TimeSpan getIncompleteAttemptsTimeSpanDelay(); @Config("org.killbill.payment.janitor.transactions.retries") @Default("15s,1m,3m,1h,1d,1d,1d,1d,1d") @Description("Delay before which unresolved transactions should be retried") - public List getIncompleteTransactionsRetries(); + List getIncompleteTransactionsRetries(); @Config("org.killbill.payment.janitor.rate") @Default("1h") @Description("Rate at which janitor tasks are scheduled") - public TimeSpan getJanitorRunningRate(); + TimeSpan getJanitorRunningRate(); @Config("org.killbill.payment.invoice.plugin") @Default("") @Description("Default payment control plugin names") - public List getPaymentControlPluginNames(); + List getPaymentControlPluginNames(); + + @Config("org.killbill.payment.globalLock.retries") + @Default("50") + @Description("Maximum number of times the system will retry to grab global lock (with a 100ms wait each time)") + int getMaxGlobalLockRetries(); @Config("org.killbill.payment.off") @Default("false") @Description("Whether the payment subsystem is off") - public boolean isPaymentOff(); + boolean isPaymentOff(); } From 4168e486cb5469a133d516ae7259f9fa44455d60 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 4 Sep 2015 14:00:09 -0700 Subject: [PATCH 058/137] Update pom with latest oss released version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2ca99c57dc..6abdc1f69f 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.38 + 0.39 killbill 0.15.4-SNAPSHOT From 5aaf406ac68f2890e744e2b446be3a3680556349 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 4 Sep 2015 16:42:50 -0700 Subject: [PATCH 059/137] invoice: Refactor DefaultInvoiceGenerator and remove BillingModeGenerator Pure refactoring, no side effect. --- .../generator/DefaultInvoiceGenerator.java | 350 +----------------- .../generator/FixedAndRecurringGenerator.java | 21 ++ ...FixedAndRecurringInvoiceItemGenerator.java | 338 +++++++++++++++++ .../invoice/generator/InvoiceGenerator.java | 5 +- .../generator/InvoiceItemGenerator.java | 44 +++ .../generator/UsageInvoiceItemGenerator.java | 193 ++++++++++ .../invoice/glue/DefaultInvoiceModule.java | 7 +- .../invoice/model/BillingModeGenerator.java | 29 -- .../model/DefaultBillingModeGenerator.java | 131 ------- .../billing/invoice/InvoiceTestSuiteNoDB.java | 4 +- .../TestDefaultInvoiceGenerator.java | 1 - ...gMode.java => TestRecurringInAdvance.java} | 23 +- ...ngMode.java => TestRecurringInArrear.java} | 29 +- .../invoice/proRations/ProRationTestBase.java | 11 +- .../inAdvance/TestValidationProRation.java | 7 - 15 files changed, 642 insertions(+), 551 deletions(-) create mode 100644 invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringGenerator.java create mode 100644 invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java create mode 100644 invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java create mode 100644 invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java delete mode 100644 invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java delete mode 100644 invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java rename invoice/src/test/java/org/killbill/billing/invoice/model/{TestInAdvanceBillingMode.java => TestRecurringInAdvance.java} (83%) rename invoice/src/test/java/org/killbill/billing/invoice/model/{TestInArrearBillingMode.java => TestRecurringInArrear.java} (79%) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index 2d6e805c15..6447349272 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -18,11 +18,7 @@ package org.killbill.billing.invoice.generator; -import java.math.BigDecimal; -import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; @@ -34,61 +30,33 @@ import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalCallContext; -import org.killbill.billing.catalog.api.BillingMode; -import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.catalog.api.CatalogApiException; import org.killbill.billing.catalog.api.Currency; -import org.killbill.billing.catalog.api.Usage; -import org.killbill.billing.catalog.api.UsageType; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItem; -import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; -import org.killbill.billing.invoice.model.BillingModeGenerator; -import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.DefaultInvoice; -import org.killbill.billing.invoice.model.FixedPriceInvoiceItem; -import org.killbill.billing.invoice.model.InvalidDateSequenceException; -import org.killbill.billing.invoice.model.RecurringInvoiceItem; -import org.killbill.billing.invoice.model.RecurringInvoiceItemData; -import org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate; -import org.killbill.billing.invoice.tree.AccountItemTree; -import org.killbill.billing.invoice.usage.RawUsageOptimizer; -import org.killbill.billing.invoice.usage.RawUsageOptimizer.RawUsageOptimizerResult; -import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear; -import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear.SubscriptionConsumableInArrearItemsAndNextNotificationDate; -import org.killbill.billing.junction.BillingEvent; import org.killbill.billing.junction.BillingEventSet; import org.killbill.billing.util.config.InvoiceConfig; -import org.killbill.billing.util.currency.KillBillMoney; import org.killbill.clock.Clock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.inject.Inject; public class DefaultInvoiceGenerator implements InvoiceGenerator { - private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class); - private final Clock clock; private final InvoiceConfig config; - private final RawUsageOptimizer rawUsageOptimizer; - final BillingModeGenerator billingModeGenerator; + + private final FixedAndRecurringInvoiceItemGenerator recurringInvoiceItemGenerator; + private final UsageInvoiceItemGenerator usageInvoiceItemGenerator; @Inject - public DefaultInvoiceGenerator(final Clock clock, final InvoiceConfig config, final RawUsageOptimizer rawUsageOptimizer) { + public DefaultInvoiceGenerator(final Clock clock, final InvoiceConfig config, final FixedAndRecurringInvoiceItemGenerator recurringInvoiceItemGenerator, final UsageInvoiceItemGenerator usageInvoiceItemGenerator) { this.clock = clock; this.config = config; - this.rawUsageOptimizer = rawUsageOptimizer; - this.billingModeGenerator = new DefaultBillingModeGenerator(); + this.recurringInvoiceItemGenerator = recurringInvoiceItemGenerator; + this.usageInvoiceItemGenerator = usageInvoiceItemGenerator; } /* @@ -110,159 +78,15 @@ public InvoiceWithMetadata generateInvoice(final Account account, @Nullable fina final UUID invoiceId = invoice.getId(); final Map perSubscriptionFutureNotificationDates = new HashMap(); - final List fixedAndRecurringItems = generateFixedAndRecurringInvoiceItems(account.getId(), invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates); + final List fixedAndRecurringItems = recurringInvoiceItemGenerator.generateItems(account, invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context); invoice.addInvoiceItems(fixedAndRecurringItems); - final List usageItems = generateUsageConsumableInArrearItems(account, invoiceId, events, existingInvoices, targetDate, perSubscriptionFutureNotificationDates, context); + final List usageItems = usageInvoiceItemGenerator.generateItems(account, invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context); invoice.addInvoiceItems(usageItems); return new InvoiceWithMetadata(invoice.getInvoiceItems().isEmpty() ? null : invoice, perSubscriptionFutureNotificationDates); } - private List generateUsageConsumableInArrearItems(final Account account, - final UUID invoiceId, final BillingEventSet eventSet, - @Nullable final List existingInvoices, final LocalDate targetDate, - final Map perSubscriptionFutureNotificationDates, - final InternalCallContext internalCallContext) throws InvoiceApiException { - - final Map> perSubscriptionConsumableInArrearUsageItems = extractPerSubscriptionExistingConsumableInArrearUsageItems(eventSet.getUsages(), existingInvoices); - try { - final List items = Lists.newArrayList(); - final Iterator events = eventSet.iterator(); - - RawUsageOptimizerResult rawUsageOptimizerResult = null; - List curEvents = Lists.newArrayList(); - UUID curSubscriptionId = null; - while (events.hasNext()) { - final BillingEvent event = events.next(); - // Skip events that are posterior to the targetDate - final LocalDate eventLocalEffectiveDate = new LocalDate(event.getEffectiveDate(), event.getAccount().getTimeZone()); - if (eventLocalEffectiveDate.isAfter(targetDate)) { - continue; - } - - // Optimize to do the usage query only once after we know there are indeed some usage items - if (rawUsageOptimizerResult == null && - Iterables.any(event.getUsages(), new Predicate() { - @Override - public boolean apply(@Nullable final Usage input) { - return (input.getUsageType() == UsageType.CONSUMABLE && - input.getBillingMode() == BillingMode.IN_ARREAR); - } - })) { - rawUsageOptimizerResult = rawUsageOptimizer.getConsumableInArrearUsage(new LocalDate(event.getEffectiveDate(), account.getTimeZone()), targetDate, Iterables.concat(perSubscriptionConsumableInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext); - } - - // None of the billing events report any usage (CONSUMABLE/IN_ARREAR) sections - if (rawUsageOptimizerResult == null) { - continue; - } - - final UUID subscriptionId = event.getSubscription().getId(); - if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) { - final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); - final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); - - final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); - final List newInArrearUsageItems = subscriptionResult.getInvoiceItems(); - items.addAll(newInArrearUsageItems); - updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); - curEvents = Lists.newArrayList(); - } - curSubscriptionId = subscriptionId; - curEvents.add(event); - } - if (curSubscriptionId != null) { - final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); - final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); - - final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); - final List newInArrearUsageItems = subscriptionResult.getInvoiceItems(); - items.addAll(newInArrearUsageItems); - updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); - } - return items; - - } catch (CatalogApiException e) { - throw new InvoiceApiException(e); - } - } - - private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final Map nextBillingCycleDates, final BillingMode usageBillingMode, final Map perSubscriptionFutureNotificationDates) { - if (usageBillingMode == BillingMode.IN_ADVANCE) { - throw new IllegalStateException("Not implemented Yet)"); - } - - SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); - if (subscriptionFutureNotificationDates == null) { - subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(null); - perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); - } - for (final String usageName : nextBillingCycleDates.keySet()) { - subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(usageName, usageBillingMode, nextBillingCycleDates.get(usageName)); - } - } - - private Map> extractPerSubscriptionExistingConsumableInArrearUsageItems(final Map knownUsage, @Nullable final List existingInvoices) { - - if (existingInvoices == null || existingInvoices.isEmpty()) { - return ImmutableMap.of(); - } - - final Map> result = new HashMap>(); - final Iterable usageConsumableInArrearItems = Iterables.concat(Iterables.transform(existingInvoices, new Function>() { - @Override - public Iterable apply(final Invoice input) { - - return Iterables.filter(input.getInvoiceItems(), new Predicate() { - @Override - public boolean apply(final InvoiceItem input) { - if (input.getInvoiceItemType() == InvoiceItemType.USAGE) { - final Usage usage = knownUsage.get(input.getUsageName()); - return usage.getUsageType() == UsageType.CONSUMABLE && usage.getBillingMode() == BillingMode.IN_ARREAR; - } - return false; - } - }); - } - })); - - for (InvoiceItem cur : usageConsumableInArrearItems) { - List perSubscriptionUsageItems = result.get(cur.getSubscriptionId()); - if (perSubscriptionUsageItems == null) { - perSubscriptionUsageItems = new LinkedList(); - result.put(cur.getSubscriptionId(), perSubscriptionUsageItems); - } - perSubscriptionUsageItems.add(cur); - } - return result; - } - - private List generateFixedAndRecurringInvoiceItems(final UUID accountId, final UUID invoiceId, final BillingEventSet eventSet, - @Nullable final List existingInvoices, final LocalDate targetDate, - final Currency targetCurrency, Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { - final AccountItemTree accountItemTree = new AccountItemTree(accountId, invoiceId); - if (existingInvoices != null) { - for (final Invoice invoice : existingInvoices) { - for (final InvoiceItem item : invoice.getInvoiceItems()) { - if (item.getSubscriptionId() == null || // Always include migration invoices, credits, external charges etc. - !eventSet.getSubscriptionIdsWithAutoInvoiceOff() - .contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag - accountItemTree.addExistingItem(item); - } - } - } - } - - // Generate list of proposed invoice items based on billing events from junction-- proposed items are ALL items since beginning of time - final List proposedItems = new ArrayList(); - processRecurringBillingEvents(invoiceId, accountId, eventSet, targetDate, targetCurrency, proposedItems, perSubscriptionFutureNotificationDate); - processFixedBillingEvents(invoiceId, accountId, eventSet, targetDate, targetCurrency, proposedItems); - - accountItemTree.mergeWithProposedItems(proposedItems); - return accountItemTree.getResultingItemList(); - } - private void validateTargetDate(final LocalDate targetDate) throws InvoiceApiException { final int maximumNumberOfMonths = config.getNumberOfMonthsInFuture(); @@ -285,162 +109,4 @@ private LocalDate adjustTargetDate(final List existingInvoices, final L } return maxDate; } - - private List processRecurringBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, - final LocalDate targetDate, final Currency currency, final List proposedItems, - final Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { - - if (events.size() == 0) { - return proposedItems; - } - - // Pretty-print the generated invoice items from the junction events - final StringBuilder logStringBuilder = new StringBuilder("Proposed Invoice items for invoiceId ") - .append(invoiceId) - .append(" and accountId ") - .append(accountId); - - final Iterator eventIt = events.iterator(); - BillingEvent nextEvent = eventIt.next(); - while (eventIt.hasNext()) { - final BillingEvent thisEvent = nextEvent; - nextEvent = eventIt.next(); - if (!events.getSubscriptionIdsWithAutoInvoiceOff(). - contains(thisEvent.getSubscription().getId())) { // don't consider events for subscriptions that have auto_invoice_off - final BillingEvent adjustedNextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null; - final List newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate); - proposedItems.addAll(newProposedItems); - } - } - final List newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate); - proposedItems.addAll(newProposedItems); - - log.info(logStringBuilder.toString()); - - return proposedItems; - } - - private List processFixedBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List proposedItems) { - final Iterator eventIt = events.iterator(); - while (eventIt.hasNext()) { - final BillingEvent thisEvent = eventIt.next(); - - final InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, accountId, thisEvent, targetDate, currency); - if (fixedPriceInvoiceItem != null) { - proposedItems.add(fixedPriceInvoiceItem); - } - } - return proposedItems; - } - - // Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day) - private List processRecurringEvent(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, - final LocalDate targetDate, final Currency currency, - final StringBuilder logStringBuilder, final BillingMode billingMode, - final Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { - final List items = new ArrayList(); - - // Handle recurring items - final BillingPeriod billingPeriod = thisEvent.getBillingPeriod(); - if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) { - final LocalDate startDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); - - if (!startDate.isAfter(targetDate)) { - final LocalDate endDate = (nextEvent == null) ? null : new LocalDate(nextEvent.getEffectiveDate(), nextEvent.getTimeZone()); - - final int billCycleDayLocal = thisEvent.getBillCycleDayLocal(); - - final RecurringInvoiceItemDataWithNextBillingCycleDate itemDataWithNextBillingCycleDate; - try { - itemDataWithNextBillingCycleDate = billingModeGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode); - } catch (InvalidDateSequenceException e) { - throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate); - } - - // STEPH Move all this logic into RecurringInvoiceItemData - for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) { - final BigDecimal rate = thisEvent.getRecurringPrice(); - - if (rate != null) { - final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency); - - final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, - accountId, - thisEvent.getSubscription().getBundleId(), - thisEvent.getSubscription().getId(), - thisEvent.getPlan().getName(), - thisEvent.getPlanPhase().getName(), - itemDatum.getStartDate(), itemDatum.getEndDate(), - amount, rate, currency); - items.add(recurringItem); - } - } - updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate); - - } - } - - // For debugging purposes - logStringBuilder.append("\n") - .append(thisEvent); - for (final InvoiceItem item : items) { - logStringBuilder.append("\n\t") - .append(item); - } - return items; - } - - private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List newProposedItems, final BillingMode billingMode, final Map perSubscriptionFutureNotificationDates) { - - LocalDate nextNotificationDate = null; - switch (billingMode) { - case IN_ADVANCE: - for (final InvoiceItem item : newProposedItems) { - if ((item.getEndDate() != null) && - (item.getAmount() == null || - item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) { - if (nextNotificationDate == null) { - nextNotificationDate = item.getEndDate(); - } else { - nextNotificationDate = nextNotificationDate.compareTo(item.getEndDate()) > 0 ? nextNotificationDate : item.getEndDate(); - } - } - } - break; - case IN_ARREAR: - nextNotificationDate = nextBillingCycleDate; - break; - default: - throw new IllegalStateException("Unrecognized billing mode " + billingMode); - } - if (nextNotificationDate != null) { - SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); - if (subscriptionFutureNotificationDates == null) { - subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(billingMode); - perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); - } - subscriptionFutureNotificationDates.updateNextRecurringDateIfRequired(nextNotificationDate); - - } - } - - private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, - final LocalDate targetDate, final Currency currency) { - final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); - - if (roundedStartDate.isAfter(targetDate)) { - return null; - } else { - final BigDecimal fixedPrice = thisEvent.getFixedPrice(); - - if (fixedPrice != null) { - return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(), - thisEvent.getSubscription().getId(), - thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(), - roundedStartDate, fixedPrice, currency); - } else { - return null; - } - } - } } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringGenerator.java new file mode 100644 index 0000000000..8fe996e31e --- /dev/null +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringGenerator.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.generator; + +public class FixedAndRecurringGenerator { +} diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java new file mode 100644 index 0000000000..19ceeee5ca --- /dev/null +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java @@ -0,0 +1,338 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.generator; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.joda.time.LocalDate; +import org.killbill.billing.ErrorCode; +import org.killbill.billing.account.api.Account; +import org.killbill.billing.callcontext.InternalCallContext; +import org.killbill.billing.catalog.api.BillingMode; +import org.killbill.billing.catalog.api.BillingPeriod; +import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.invoice.api.Invoice; +import org.killbill.billing.invoice.api.InvoiceApiException; +import org.killbill.billing.invoice.api.InvoiceItem; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; +import org.killbill.billing.invoice.model.FixedPriceInvoiceItem; +import org.killbill.billing.invoice.model.InvalidDateSequenceException; +import org.killbill.billing.invoice.model.RecurringInvoiceItem; +import org.killbill.billing.invoice.model.RecurringInvoiceItemData; +import org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate; +import org.killbill.billing.invoice.tree.AccountItemTree; +import org.killbill.billing.junction.BillingEvent; +import org.killbill.billing.junction.BillingEventSet; +import org.killbill.billing.util.currency.KillBillMoney; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.inject.Inject; + +import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateNumberOfWholeBillingPeriods; +import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateProRationAfterLastBillingCycleDate; +import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod; + +public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator { + + private static final Logger log = LoggerFactory.getLogger(FixedAndRecurringInvoiceItemGenerator.class); + + @Inject + public FixedAndRecurringInvoiceItemGenerator() { + } + + public List generateItems(final Account account, final UUID invoiceId, final BillingEventSet eventSet, + @Nullable final List existingInvoices, final LocalDate targetDate, + final Currency targetCurrency, Map perSubscriptionFutureNotificationDate, + final InternalCallContext internalCallContext) throws InvoiceApiException { + final AccountItemTree accountItemTree = new AccountItemTree(account.getId(), invoiceId); + if (existingInvoices != null) { + for (final Invoice invoice : existingInvoices) { + for (final InvoiceItem item : invoice.getInvoiceItems()) { + if (item.getSubscriptionId() == null || // Always include migration invoices, credits, external charges etc. + !eventSet.getSubscriptionIdsWithAutoInvoiceOff() + .contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag + accountItemTree.addExistingItem(item); + } + } + } + } + + // Generate list of proposed invoice items based on billing events from junction-- proposed items are ALL items since beginning of time + final List proposedItems = new ArrayList(); + processRecurringBillingEvents(invoiceId, account.getId(), eventSet, targetDate, targetCurrency, proposedItems, perSubscriptionFutureNotificationDate); + processFixedBillingEvents(invoiceId, account.getId(), eventSet, targetDate, targetCurrency, proposedItems); + + accountItemTree.mergeWithProposedItems(proposedItems); + return accountItemTree.getResultingItemList(); + } + + private List processRecurringBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, + final LocalDate targetDate, final Currency currency, final List proposedItems, + final Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { + + if (events.size() == 0) { + return proposedItems; + } + + // Pretty-print the generated invoice items from the junction events + final StringBuilder logStringBuilder = new StringBuilder("Proposed Invoice items for invoiceId ") + .append(invoiceId) + .append(" and accountId ") + .append(accountId); + + final Iterator eventIt = events.iterator(); + BillingEvent nextEvent = eventIt.next(); + while (eventIt.hasNext()) { + final BillingEvent thisEvent = nextEvent; + nextEvent = eventIt.next(); + if (!events.getSubscriptionIdsWithAutoInvoiceOff(). + contains(thisEvent.getSubscription().getId())) { // don't consider events for subscriptions that have auto_invoice_off + final BillingEvent adjustedNextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null; + final List newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate); + proposedItems.addAll(newProposedItems); + } + } + final List newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate); + proposedItems.addAll(newProposedItems); + + log.info(logStringBuilder.toString()); + + return proposedItems; + } + + private List processFixedBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List proposedItems) { + final Iterator eventIt = events.iterator(); + while (eventIt.hasNext()) { + final BillingEvent thisEvent = eventIt.next(); + + final InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, accountId, thisEvent, targetDate, currency); + if (fixedPriceInvoiceItem != null) { + proposedItems.add(fixedPriceInvoiceItem); + } + } + return proposedItems; + } + + // Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day) + private List processRecurringEvent(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, + final LocalDate targetDate, final Currency currency, + final StringBuilder logStringBuilder, final BillingMode billingMode, + final Map perSubscriptionFutureNotificationDate) throws InvoiceApiException { + final List items = new ArrayList(); + + // Handle recurring items + final BillingPeriod billingPeriod = thisEvent.getBillingPeriod(); + if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) { + final LocalDate startDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); + + if (!startDate.isAfter(targetDate)) { + final LocalDate endDate = (nextEvent == null) ? null : new LocalDate(nextEvent.getEffectiveDate(), nextEvent.getTimeZone()); + + final int billCycleDayLocal = thisEvent.getBillCycleDayLocal(); + + final RecurringInvoiceItemDataWithNextBillingCycleDate itemDataWithNextBillingCycleDate; + try { + itemDataWithNextBillingCycleDate = generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode); + } catch (InvalidDateSequenceException e) { + throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate); + } + + for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) { + final BigDecimal rate = thisEvent.getRecurringPrice(); + + if (rate != null) { + final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency); + + final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, + accountId, + thisEvent.getSubscription().getBundleId(), + thisEvent.getSubscription().getId(), + thisEvent.getPlan().getName(), + thisEvent.getPlanPhase().getName(), + itemDatum.getStartDate(), itemDatum.getEndDate(), + amount, rate, currency); + items.add(recurringItem); + } + } + updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate); + + } + } + + // For debugging purposes + logStringBuilder.append("\n") + .append(thisEvent); + for (final InvoiceItem item : items) { + logStringBuilder.append("\n\t") + .append(item); + } + return items; + } + + private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List newProposedItems, final BillingMode billingMode, final Map perSubscriptionFutureNotificationDates) { + + LocalDate nextNotificationDate = null; + switch (billingMode) { + case IN_ADVANCE: + for (final InvoiceItem item : newProposedItems) { + if ((item.getEndDate() != null) && + (item.getAmount() == null || + item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) { + if (nextNotificationDate == null) { + nextNotificationDate = item.getEndDate(); + } else { + nextNotificationDate = nextNotificationDate.compareTo(item.getEndDate()) > 0 ? nextNotificationDate : item.getEndDate(); + } + } + } + break; + case IN_ARREAR: + nextNotificationDate = nextBillingCycleDate; + break; + default: + throw new IllegalStateException("Unrecognized billing mode " + billingMode); + } + if (nextNotificationDate != null) { + SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); + if (subscriptionFutureNotificationDates == null) { + subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(billingMode); + perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); + } + subscriptionFutureNotificationDates.updateNextRecurringDateIfRequired(nextNotificationDate); + + } + } + + public RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate, + final LocalDate targetDate, + final int billingCycleDayLocal, + final BillingPeriod billingPeriod, + final BillingMode billingMode) throws InvalidDateSequenceException { + if (endDate != null && endDate.isBefore(startDate)) { + throw new InvalidDateSequenceException(); + } + if (targetDate.isBefore(startDate)) { + throw new InvalidDateSequenceException(); + } + + final List results = new ArrayList(); + + final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, billingMode); + + // We are not billing for less than a day + if (!billingIntervalDetail.hasSomethingToBill()) { + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); + } + // + // If there is an endDate and that endDate is before our first coming firstBillingCycleDate, all we have to do + // is to charge for that period + // + if (endDate != null && !endDate.isAfter(billingIntervalDetail.getFirstBillingCycleDate())) { + final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod); + final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods); + results.add(itemData); + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); + } + + // + // Leading proration if + // i) The first firstBillingCycleDate is strictly after our start date AND + // ii) The endDate is is not null and is strictly after our firstBillingCycleDate (previous check) + // + if (billingIntervalDetail.getFirstBillingCycleDate().isAfter(startDate)) { + final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, billingIntervalDetail.getFirstBillingCycleDate(), billingPeriod); + if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) { + // Not common - add info in the logs for debugging purposes + final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, billingIntervalDetail.getFirstBillingCycleDate(), leadingProRationPeriods); + log.info("Adding pro-ration: {}", itemData); + results.add(itemData); + } + } + + // + // Calculate the effectiveEndDate from the firstBillingCycleDate: + // - If endDate != null and targetDate is after endDate => this is the endDate and will lead to a trailing pro-ration + // - If not, this is the last billingCycleDate calculation right after the targetDate + // + final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate(); + + // + // Based on what we calculated previously, code recompute one more time the numberOfWholeBillingPeriods + // + final LocalDate lastBillingCycleDate = billingIntervalDetail.getLastBillingCycleDate(); + final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(billingIntervalDetail.getFirstBillingCycleDate(), lastBillingCycleDate, billingPeriod); + + for (int i = 0; i < numberOfWholeBillingPeriods; i++) { + final LocalDate servicePeriodStartDate; + if (results.size() > 0) { + // Make sure the periods align, especially with the pro-ration calculations above + servicePeriodStartDate = results.get(results.size() - 1).getEndDate(); + } else if (i == 0) { + // Use the specified start date + servicePeriodStartDate = startDate; + } else { + throw new IllegalStateException("We should at least have one invoice item!"); + } + + // Make sure to align the end date with the BCD + final LocalDate servicePeriodEndDate = billingIntervalDetail.getFutureBillingDateFor(i + 1); + results.add(new RecurringInvoiceItemData(servicePeriodStartDate, servicePeriodEndDate, BigDecimal.ONE)); + } + + // + // Now we check if indeed we need a trailing proration and add that incomplete item + // + if (effectiveEndDate.isAfter(lastBillingCycleDate)) { + final BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod); + if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) { + // Not common - add info in the logs for debugging purposes + final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods); + log.info("Adding trailing pro-ration: {}", itemData); + results.add(itemData); + } + } + return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); + } + + private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, + final LocalDate targetDate, final Currency currency) { + final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone()); + if (roundedStartDate.isAfter(targetDate)) { + return null; + } else { + final BigDecimal fixedPrice = thisEvent.getFixedPrice(); + + if (fixedPrice != null) { + return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(), + thisEvent.getSubscription().getId(), + thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(), + roundedStartDate, fixedPrice, currency); + } else { + return null; + } + } + } +} diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java index 3ff29a6e24..09f41eee6b 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java @@ -31,7 +31,6 @@ import org.killbill.billing.junction.BillingEventSet; public interface InvoiceGenerator { - - public InvoiceWithMetadata generateInvoice(Account account, @Nullable BillingEventSet events, @Nullable List existingInvoices, - LocalDate targetDate, Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException; + InvoiceWithMetadata generateInvoice(Account account, @Nullable BillingEventSet events, @Nullable List existingInvoices, + LocalDate targetDate, Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException; } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java new file mode 100644 index 0000000000..6009549ed9 --- /dev/null +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.generator; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.joda.time.LocalDate; +import org.killbill.billing.account.api.Account; +import org.killbill.billing.callcontext.InternalCallContext; +import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.invoice.api.Invoice; +import org.killbill.billing.invoice.api.InvoiceApiException; +import org.killbill.billing.invoice.api.InvoiceItem; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; +import org.killbill.billing.junction.BillingEventSet; + +public abstract class InvoiceItemGenerator { + + public abstract List generateItems(final Account account, final UUID invoiceId, final BillingEventSet eventSet, + @Nullable final List existingInvoices, final LocalDate targetDate, + final Currency targetCurrency, Map perSubscriptionFutureNotificationDate, + final InternalCallContext context) throws InvoiceApiException; + + +} diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java new file mode 100644 index 0000000000..7750be8ba6 --- /dev/null +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java @@ -0,0 +1,193 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.generator; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.joda.time.LocalDate; +import org.killbill.billing.account.api.Account; +import org.killbill.billing.callcontext.InternalCallContext; +import org.killbill.billing.catalog.api.BillingMode; +import org.killbill.billing.catalog.api.CatalogApiException; +import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.catalog.api.Usage; +import org.killbill.billing.catalog.api.UsageType; +import org.killbill.billing.invoice.api.Invoice; +import org.killbill.billing.invoice.api.InvoiceApiException; +import org.killbill.billing.invoice.api.InvoiceItem; +import org.killbill.billing.invoice.api.InvoiceItemType; +import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; +import org.killbill.billing.invoice.usage.RawUsageOptimizer; +import org.killbill.billing.invoice.usage.RawUsageOptimizer.RawUsageOptimizerResult; +import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear; +import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear.SubscriptionConsumableInArrearItemsAndNextNotificationDate; +import org.killbill.billing.junction.BillingEvent; +import org.killbill.billing.junction.BillingEventSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.inject.Inject; + +public class UsageInvoiceItemGenerator extends InvoiceItemGenerator { + + private static final Logger log = LoggerFactory.getLogger(UsageInvoiceItemGenerator.class); + + private final RawUsageOptimizer rawUsageOptimizer; + + @Inject + public UsageInvoiceItemGenerator(final RawUsageOptimizer rawUsageOptimizer) { + this.rawUsageOptimizer = rawUsageOptimizer; + } + + + @Override + public List generateItems(final Account account, + final UUID invoiceId, + final BillingEventSet eventSet, + @Nullable final List existingInvoices, + final LocalDate targetDate, + final Currency targetCurrency, + final Map perSubscriptionFutureNotificationDates, + final InternalCallContext internalCallContext) throws InvoiceApiException { + + final Map> perSubscriptionConsumableInArrearUsageItems = extractPerSubscriptionExistingConsumableInArrearUsageItems(eventSet.getUsages(), existingInvoices); + try { + final List items = Lists.newArrayList(); + final Iterator events = eventSet.iterator(); + + RawUsageOptimizerResult rawUsageOptimizerResult = null; + List curEvents = Lists.newArrayList(); + UUID curSubscriptionId = null; + while (events.hasNext()) { + final BillingEvent event = events.next(); + // Skip events that are posterior to the targetDate + final LocalDate eventLocalEffectiveDate = new LocalDate(event.getEffectiveDate(), event.getAccount().getTimeZone()); + if (eventLocalEffectiveDate.isAfter(targetDate)) { + continue; + } + + // Optimize to do the usage query only once after we know there are indeed some usage items + if (rawUsageOptimizerResult == null && + Iterables.any(event.getUsages(), new Predicate() { + @Override + public boolean apply(@Nullable final Usage input) { + return (input.getUsageType() == UsageType.CONSUMABLE && + input.getBillingMode() == BillingMode.IN_ARREAR); + } + })) { + rawUsageOptimizerResult = rawUsageOptimizer.getConsumableInArrearUsage(new LocalDate(event.getEffectiveDate(), account.getTimeZone()), targetDate, Iterables.concat(perSubscriptionConsumableInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext); + } + + // None of the billing events report any usage (CONSUMABLE/IN_ARREAR) sections + if (rawUsageOptimizerResult == null) { + continue; + } + + final UUID subscriptionId = event.getSubscription().getId(); + if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) { + final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); + final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); + + final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + final List newInArrearUsageItems = subscriptionResult.getInvoiceItems(); + items.addAll(newInArrearUsageItems); + updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); + curEvents = Lists.newArrayList(); + } + curSubscriptionId = subscriptionId; + curEvents.add(event); + } + if (curSubscriptionId != null) { + final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); + final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); + + final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); + final List newInArrearUsageItems = subscriptionResult.getInvoiceItems(); + items.addAll(newInArrearUsageItems); + updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates); + } + return items; + + } catch (CatalogApiException e) { + throw new InvoiceApiException(e); + } + } + + private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final Map nextBillingCycleDates, final BillingMode usageBillingMode, final Map perSubscriptionFutureNotificationDates) { + if (usageBillingMode == BillingMode.IN_ADVANCE) { + throw new IllegalStateException("Not implemented Yet)"); + } + + SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId); + if (subscriptionFutureNotificationDates == null) { + subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(null); + perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates); + } + for (final String usageName : nextBillingCycleDates.keySet()) { + subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(usageName, usageBillingMode, nextBillingCycleDates.get(usageName)); + } + } + + private Map> extractPerSubscriptionExistingConsumableInArrearUsageItems(final Map knownUsage, @Nullable final List existingInvoices) { + + if (existingInvoices == null || existingInvoices.isEmpty()) { + return ImmutableMap.of(); + } + + final Map> result = new HashMap>(); + final Iterable usageConsumableInArrearItems = Iterables.concat(Iterables.transform(existingInvoices, new Function>() { + @Override + public Iterable apply(final Invoice input) { + + return Iterables.filter(input.getInvoiceItems(), new Predicate() { + @Override + public boolean apply(final InvoiceItem input) { + if (input.getInvoiceItemType() == InvoiceItemType.USAGE) { + final Usage usage = knownUsage.get(input.getUsageName()); + return usage.getUsageType() == UsageType.CONSUMABLE && usage.getBillingMode() == BillingMode.IN_ARREAR; + } + return false; + } + }); + } + })); + + for (InvoiceItem cur : usageConsumableInArrearItems) { + List perSubscriptionUsageItems = result.get(cur.getSubscriptionId()); + if (perSubscriptionUsageItems == null) { + perSubscriptionUsageItems = new LinkedList(); + result.put(cur.getSubscriptionId(), perSubscriptionUsageItems); + } + perSubscriptionUsageItems.add(cur); + } + return result; + } +} diff --git a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java index f106da5d06..1973ff2488 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java @@ -39,7 +39,9 @@ import org.killbill.billing.invoice.dao.DefaultInvoiceDao; import org.killbill.billing.invoice.dao.InvoiceDao; import org.killbill.billing.invoice.generator.DefaultInvoiceGenerator; +import org.killbill.billing.invoice.generator.FixedAndRecurringInvoiceItemGenerator; import org.killbill.billing.invoice.generator.InvoiceGenerator; +import org.killbill.billing.invoice.generator.UsageInvoiceItemGenerator; import org.killbill.billing.invoice.notification.DefaultNextBillingDateNotifier; import org.killbill.billing.invoice.notification.DefaultNextBillingDatePoster; import org.killbill.billing.invoice.notification.EmailInvoiceNotifier; @@ -133,6 +135,8 @@ protected void installTagHandler() { protected void installInvoiceGenerator() { bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton(); + bind(FixedAndRecurringInvoiceItemGenerator.class).asEagerSingleton(); + bind(UsageInvoiceItemGenerator.class).asEagerSingleton(); } protected void installInvoicePluginApi() { @@ -157,7 +161,8 @@ protected void configure() { installInvoicePaymentApi(); installInvoiceMigrationApi(); installResourceBundleFactory(); - bind(RawUsageOptimizer.class).asEagerSingleton();; + bind(RawUsageOptimizer.class).asEagerSingleton(); + ; bind(InvoiceApiHelper.class).asEagerSingleton(); } } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java deleted file mode 100644 index 19a8496a6e..0000000000 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.invoice.model; - -import javax.annotation.Nullable; - -import org.joda.time.LocalDate; -import org.killbill.billing.catalog.api.BillingMode; -import org.killbill.billing.catalog.api.BillingPeriod; - -public interface BillingModeGenerator { - - RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData(LocalDate startDate, @Nullable LocalDate endDate, LocalDate targetDate, - int billingCycleDay, BillingPeriod billingPeriod, BillingMode billingMode) throws InvalidDateSequenceException; -} diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java deleted file mode 100644 index db75fef506..0000000000 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.invoice.model; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.Nullable; - -import org.joda.time.LocalDate; -import org.killbill.billing.catalog.api.BillingMode; -import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.invoice.generator.BillingIntervalDetail; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateNumberOfWholeBillingPeriods; -import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateProRationAfterLastBillingCycleDate; -import static org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod; - -public class DefaultBillingModeGenerator implements BillingModeGenerator { - - private static final Logger log = LoggerFactory.getLogger(DefaultBillingModeGenerator.class); - - @Override - public RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate, - final LocalDate targetDate, - final int billingCycleDayLocal, - final BillingPeriod billingPeriod, - final BillingMode billingMode) throws InvalidDateSequenceException { - if (endDate != null && endDate.isBefore(startDate)) { - throw new InvalidDateSequenceException(); - } - if (targetDate.isBefore(startDate)) { - throw new InvalidDateSequenceException(); - } - - final List results = new ArrayList(); - - final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, billingMode); - - // We are not billing for less than a day - if (!billingIntervalDetail.hasSomethingToBill()) { - return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); - } - // - // If there is an endDate and that endDate is before our first coming firstBillingCycleDate, all we have to do - // is to charge for that period - // - if (endDate != null && !endDate.isAfter(billingIntervalDetail.getFirstBillingCycleDate())) { - final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod); - final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods); - results.add(itemData); - return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); - } - - // - // Leading proration if - // i) The first firstBillingCycleDate is strictly after our start date AND - // ii) The endDate is is not null and is strictly after our firstBillingCycleDate (previous check) - // - if (billingIntervalDetail.getFirstBillingCycleDate().isAfter(startDate)) { - final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, billingIntervalDetail.getFirstBillingCycleDate(), billingPeriod); - if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) { - // Not common - add info in the logs for debugging purposes - final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, billingIntervalDetail.getFirstBillingCycleDate(), leadingProRationPeriods); - log.info("Adding pro-ration: {}", itemData); - results.add(itemData); - } - } - - // - // Calculate the effectiveEndDate from the firstBillingCycleDate: - // - If endDate != null and targetDate is after endDate => this is the endDate and will lead to a trailing pro-ration - // - If not, this is the last billingCycleDate calculation right after the targetDate - // - final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate(); - - // - // Based on what we calculated previously, code recompute one more time the numberOfWholeBillingPeriods - // - final LocalDate lastBillingCycleDate = billingIntervalDetail.getLastBillingCycleDate(); - final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(billingIntervalDetail.getFirstBillingCycleDate(), lastBillingCycleDate, billingPeriod); - - for (int i = 0; i < numberOfWholeBillingPeriods; i++) { - final LocalDate servicePeriodStartDate; - if (results.size() > 0) { - // Make sure the periods align, especially with the pro-ration calculations above - servicePeriodStartDate = results.get(results.size() - 1).getEndDate(); - } else if (i == 0) { - // Use the specified start date - servicePeriodStartDate = startDate; - } else { - throw new IllegalStateException("We should at least have one invoice item!"); - } - - // Make sure to align the end date with the BCD - final LocalDate servicePeriodEndDate = billingIntervalDetail.getFutureBillingDateFor(i + 1); - results.add(new RecurringInvoiceItemData(servicePeriodStartDate, servicePeriodEndDate, BigDecimal.ONE)); - } - - // - // Now we check if indeed we need a trailing proration and add that incomplete item - // - if (effectiveEndDate.isAfter(lastBillingCycleDate)) { - final BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod); - if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) { - // Not common - add info in the logs for debugging purposes - final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods); - log.info("Adding trailing pro-ration: {}", itemData); - results.add(itemData); - } - } - return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail); - } -} diff --git a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java index 1069bfc656..2e2692e95a 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java @@ -27,6 +27,7 @@ import org.killbill.billing.invoice.api.InvoiceUserApi; import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory; import org.killbill.billing.invoice.dao.InvoiceDao; +import org.killbill.billing.invoice.generator.FixedAndRecurringInvoiceItemGenerator; import org.killbill.billing.invoice.generator.InvoiceGenerator; import org.killbill.billing.invoice.glue.TestInvoiceModuleNoDB; import org.killbill.billing.invoice.usage.RawUsageOptimizer; @@ -97,7 +98,8 @@ public abstract class InvoiceTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB { protected ResourceBundleFactory resourceBundleFactory; @Inject protected RawUsageOptimizer rawUsageOptimizer; - + @Inject + protected FixedAndRecurringInvoiceItemGenerator fixedAndRecurringInvoiceItemGenerator; @Override protected KillbillConfigSource getConfigSource() { return getConfigSource("/resource.properties"); diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java index 9c5ec237f1..4d45ea45ac 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java @@ -133,7 +133,6 @@ public int getMaxGlobalLockRetries() { return 10; } }; - this.generator = new DefaultInvoiceGenerator(clock, invoiceConfig, null); this.account = new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8)) .firstNameLength(6) .email(UUID.randomUUID().toString().substring(1, 8)) diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestRecurringInAdvance.java similarity index 83% rename from invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java rename to invoice/src/test/java/org/killbill/billing/invoice/model/TestRecurringInAdvance.java index 5766409f5c..534b3f84ee 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestRecurringInAdvance.java @@ -30,7 +30,7 @@ import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.InvoiceTestSuiteNoDB; -public class TestInAdvanceBillingMode extends InvoiceTestSuiteNoDB { +public class TestRecurringInAdvance extends InvoiceTestSuiteNoDB { private static final DateTimeZone TIMEZONE = DateTimeZone.forID("Pacific/Pitcairn"); private static final BillingPeriod BILLING_PERIOD = BillingPeriod.MONTHLY; @@ -43,7 +43,7 @@ public void testItemShouldNotStartInThePast() throws Exception { final int billingCycleDayLocal = 15; final LinkedHashMap expectedDates = new LinkedHashMap(); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -56,7 +56,7 @@ public void testCalculateSimpleInvoiceItemWithNoEndDate() throws Exception { final LinkedHashMap expectedDates = new LinkedHashMap(); expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -69,7 +69,7 @@ public void testCalculateSimpleInvoiceItemWithBCDBeforeStartDay() throws Excepti final LinkedHashMap expectedDates = new LinkedHashMap(); expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -82,7 +82,7 @@ public void testCalculateSimpleInvoiceItemWithBCDEqualsStartDay() throws Excepti final LinkedHashMap expectedDates = new LinkedHashMap(); expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 16)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -95,7 +95,7 @@ public void testCalculateSimpleInvoiceItemWithBCDAfterStartDay() throws Exceptio final LinkedHashMap expectedDates = new LinkedHashMap(); expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 17)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -111,7 +111,7 @@ public void testCalculateSimpleInvoiceItemWithBCDBeforeStartDayWithTargetDateIn3 expectedDates.put(new LocalDate(2012, 9, 15), new LocalDate(2012, 10, 15)); expectedDates.put(new LocalDate(2012, 10, 15), new LocalDate(2012, 11, 15)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -127,7 +127,7 @@ public void testCalculateSimpleInvoiceItemWithBCDEqualsStartDayWithTargetDateIn3 expectedDates.put(new LocalDate(2012, 9, 16), new LocalDate(2012, 10, 16)); expectedDates.put(new LocalDate(2012, 10, 16), new LocalDate(2012, 11, 16)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -143,15 +143,14 @@ public void testCalculateSimpleInvoiceItemWithBCDAfterStartDayWithTargetDateIn3M expectedDates.put(new LocalDate(2012, 8, 17), new LocalDate(2012, 9, 17)); expectedDates.put(new LocalDate(2012, 9, 17), new LocalDate(2012, 10, 17)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } private void verifyInvoiceItems(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, - final DateTimeZone dateTimeZone, final int billingCycleDayLocal, final BillingPeriod billingPeriod, + final int billingCycleDayLocal, final BillingPeriod billingPeriod, final LinkedHashMap expectedDates) throws InvalidDateSequenceException { - final DefaultBillingModeGenerator billingMode = new DefaultBillingModeGenerator(); - final RecurringInvoiceItemDataWithNextBillingCycleDate invoiceItemsWithDates = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ADVANCE); + final RecurringInvoiceItemDataWithNextBillingCycleDate invoiceItemsWithDates = fixedAndRecurringInvoiceItemGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ADVANCE); final List invoiceItems = invoiceItemsWithDates.getItemData(); int i = 0; for (final LocalDate periodStartDate : expectedDates.keySet()) { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestRecurringInArrear.java similarity index 79% rename from invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java rename to invoice/src/test/java/org/killbill/billing/invoice/model/TestRecurringInArrear.java index 69a72aae05..b5ee272ba0 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestRecurringInArrear.java @@ -30,7 +30,7 @@ import org.testng.Assert; import org.testng.annotations.Test; -public class TestInArrearBillingMode extends InvoiceTestSuiteNoDB { +public class TestRecurringInArrear extends InvoiceTestSuiteNoDB { private static final DateTimeZone TIMEZONE = DateTimeZone.forID("Pacific/Pitcairn"); private static final BillingPeriod BILLING_PERIOD = BillingPeriod.MONTHLY; @@ -43,7 +43,7 @@ public void testItemShouldNotStartInThePast() throws Exception { final int billingCycleDayLocal = 15; final LinkedHashMap expectedDates = new LinkedHashMap(); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -54,7 +54,7 @@ public void testCalculateSimpleInvoiceItemWithNoEndDate() throws Exception { final int billingCycleDayLocal = 15; final LinkedHashMap expectedDates = new LinkedHashMap(); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -65,11 +65,11 @@ public void testCalculateSimpleInvoiceItemWithBCDBeforeStartDay() throws Excepti final int billingCycleDayLocal = 15; final LinkedHashMap expectedDates = new LinkedHashMap(); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); final LocalDate targetDate2 = new LocalDate(2012, 8, 15); expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15)); - verifyInvoiceItems(startDate, endDate, targetDate2, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate2, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -80,11 +80,11 @@ public void testCalculateSimpleInvoiceItemWithBCDEqualsStartDay() throws Excepti final int billingCycleDayLocal = 16; final LinkedHashMap expectedDates = new LinkedHashMap(); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 16)); final LocalDate targetDate2 = new LocalDate(2012, 8, 16); - verifyInvoiceItems(startDate, endDate, targetDate2, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate2, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @@ -96,11 +96,11 @@ public void testCalculateSimpleInvoiceItemWithBCDAfterStartDay() throws Exceptio final int billingCycleDayLocal = 17; final LinkedHashMap expectedDates = new LinkedHashMap(); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); final LocalDate targetDate2 = new LocalDate(2012, 7, 17); expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 17)); - verifyInvoiceItems(startDate, endDate, targetDate2, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate2, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -115,7 +115,7 @@ public void testCalculateSimpleInvoiceItemWithBCDBeforeStartDayWithTargetDateIn3 expectedDates.put(new LocalDate(2012, 8, 15), new LocalDate(2012, 9, 15)); expectedDates.put(new LocalDate(2012, 9, 15), new LocalDate(2012, 10, 15)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -130,7 +130,7 @@ public void testCalculateSimpleInvoiceItemWithBCDEqualsStartDayWithTargetDateIn3 expectedDates.put(new LocalDate(2012, 8, 16), new LocalDate(2012, 9, 16)); expectedDates.put(new LocalDate(2012, 9, 16), new LocalDate(2012, 10, 16)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } @Test(groups = "fast") @@ -145,15 +145,14 @@ public void testCalculateSimpleInvoiceItemWithBCDAfterStartDayWithTargetDateIn3M expectedDates.put(new LocalDate(2012, 7, 17), new LocalDate(2012, 8, 17)); expectedDates.put(new LocalDate(2012, 8, 17), new LocalDate(2012, 9, 17)); - verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates); + verifyInvoiceItems(startDate, endDate, targetDate, billingCycleDayLocal, BILLING_PERIOD, expectedDates); } private void verifyInvoiceItems(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, - final DateTimeZone dateTimeZone, final int billingCycleDayLocal, final BillingPeriod billingPeriod, + final int billingCycleDayLocal, final BillingPeriod billingPeriod, final LinkedHashMap expectedDates) throws InvalidDateSequenceException { - final DefaultBillingModeGenerator billingMode = new DefaultBillingModeGenerator(); - final RecurringInvoiceItemDataWithNextBillingCycleDate invoiceItemsWithDates = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ARREAR); + final RecurringInvoiceItemDataWithNextBillingCycleDate invoiceItemsWithDates = fixedAndRecurringInvoiceItemGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ARREAR); final List invoiceItems = invoiceItemsWithDates.getItemData(); int i = 0; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java index 2a31e14b67..14c5275b21 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java @@ -19,15 +19,12 @@ import static org.killbill.billing.invoice.TestInvoiceHelper.*; import java.math.BigDecimal; -import java.util.List; import org.joda.time.LocalDate; import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.invoice.InvoiceTestSuiteNoDB; -import org.killbill.billing.invoice.model.BillingModeGenerator; -import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.InvalidDateSequenceException; import org.killbill.billing.invoice.model.RecurringInvoiceItemData; import org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate; @@ -37,10 +34,6 @@ public abstract class ProRationTestBase extends InvoiceTestSuiteNoDB { - protected BillingModeGenerator getBillingModeGenerator() { - return new DefaultBillingModeGenerator(); - } - protected abstract BillingPeriod getBillingPeriod(); protected abstract BillingMode getBillingMode(); @@ -73,7 +66,7 @@ protected void testCalculateNumberOfBillingCycles(final LocalDate startDate, fin } protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException { - final RecurringInvoiceItemDataWithNextBillingCycleDate items = getBillingModeGenerator().generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); + final RecurringInvoiceItemDataWithNextBillingCycleDate items = fixedAndRecurringInvoiceItemGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); BigDecimal numberOfBillingCycles = ZERO; for (final RecurringInvoiceItemData item : items.getItemData()) { @@ -84,7 +77,7 @@ protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, f } protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException { - final RecurringInvoiceItemDataWithNextBillingCycleDate items = getBillingModeGenerator().generateInvoiceItemData(startDate, null, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); + final RecurringInvoiceItemDataWithNextBillingCycleDate items = fixedAndRecurringInvoiceItemGenerator.generateInvoiceItemData(startDate, null, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode()); BigDecimal numberOfBillingCycles = ZERO; for (final RecurringInvoiceItemData item : items.getItemData()) { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/TestValidationProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/TestValidationProRation.java index 471b9f0d66..ea1fd4bf29 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/TestValidationProRation.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/TestValidationProRation.java @@ -18,11 +18,9 @@ import org.joda.time.LocalDate; import org.killbill.billing.catalog.api.BillingMode; -import org.killbill.billing.invoice.model.BillingModeGenerator; import org.testng.annotations.Test; import org.killbill.billing.catalog.api.BillingPeriod; -import org.killbill.billing.invoice.model.DefaultBillingModeGenerator; import org.killbill.billing.invoice.model.InvalidDateSequenceException; import org.killbill.billing.invoice.proRations.ProRationTestBase; @@ -40,11 +38,6 @@ protected BillingMode getBillingMode() { return BillingMode.IN_ADVANCE; } - @Override - protected BillingModeGenerator getBillingModeGenerator() { - return new DefaultBillingModeGenerator(); - } - @Test(groups = "fast", expectedExceptions = InvalidDateSequenceException.class) public void testTargetStartEnd() throws InvalidDateSequenceException { final LocalDate startDate = invoiceUtil.buildDate(2011, 1, 30); From 3802ad20fdbb6edc70d968d9525c82c39480a347 Mon Sep 17 00:00:00 2001 From: David Xie Date: Tue, 1 Sep 2015 17:03:58 +0800 Subject: [PATCH 060/137] Add PostgreSQL support to db-helper --- bin/db-helper | 110 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 27 deletions(-) diff --git a/bin/db-helper b/bin/db-helper index 95761e605c..4f5130c674 100755 --- a/bin/db-helper +++ b/bin/db-helper @@ -31,25 +31,34 @@ HOST="localhost" DATABASE="killbill" USER="root" PWD="root" +PORT= +PORT_MYSQL=3306 +PORT_POSTGRES=5432 +DRIVER="mysql" TEST_ALSO= OUTPUT_FILE= - DDL_FILE= CLEAN_FILE= # Egrep like for skipping some modules until they are ready SKIP="(server)" +ARGS=`getopt -o "a:d:h:u:p:t:f:" -l "action:,driver:,database:,host:,user:,password:,test:,file:,port:,help" -n "db-helper" -- "$@"` +eval set -- "${ARGS}" + + function usage() { - echo -n "./db_helper " - echo -n " -a " - echo -n " -H MySQL host (default = localhost)" - echo -n " -d database_name (default = killbill)" - echo -n " -u user_name (default = root)" - echo -n " -p password (default = root)" - echo -n " -t (also include test ddl)" - echo -n " -f file (output file, for dump only)" - echo -n " -h this message" + echo -n "./db_helper" + echo -n " -a|--action " + echo -n " --driver (default = mysql)" + echo -n " -h|--host host (default = localhost)" + echo -n " --port port" + echo -n " -d|--database database_name (default = killbill)" + echo -n " -u|--user user_name (default = root)" + echo -n " -p|--password password (default = root)" + echo -n " -t|--test (also include test ddl)" + echo -n " -f|--file file (output file, for dump only)" + echo -n " --help this message" echo exit 1 } @@ -69,10 +78,10 @@ function find_test_ddl() { ddl_test="$ddl_test $cur_ddl" done echo "$ddl_test" - } -function find_src_ddl() { + +function find_src_ddl() { local modules=`get_modules` local ddl_src= @@ -98,6 +107,7 @@ function create_clean_file() { echo $tmp } + function create_ddl_file() { local ddls=`find_src_ddl` local test_ddls= @@ -108,7 +118,10 @@ function create_ddl_file() { local tmp="/tmp/ddl-$DATABASE.$$" touch $tmp - echo "use $DATABASE;" >> $tmp + if [ $DRIVER == "postgres" ]; then + cat util/src/main/resources/org/killbill/billing/util/ddl-postgresql.sql > $tmp + fi + echo "/*! use $DATABASE; */" >> $tmp echo "" >> $tmp for d in $ddls; do cat $d >> $tmp @@ -117,33 +130,62 @@ function create_ddl_file() { echo $tmp } + +function create_pgfile() { + mv -f $HOME/.pgpass $HOME/.pgpass_bak > /dev/null 2>&1 + echo "$HOST:$PORT:*:$USER:$PWD" > $HOME/.pgpass + chmod 600 $HOME/.pgpass +} + + +function clean_pgfile() { + rm -f $HOME/.pgpass > /dev/null 2>&1 + mv -f $HOME/.pgpass_bak $HOME/.pgpass > /dev/null 2>&1 +} + + function cleanup() { rm -f "/tmp/*.$$" } -while getopts ":a:d:H:u:p:f:t" options; do - case $options in - a ) ACTION=$OPTARG;; - d ) DATABASE=$OPTARG;; - H ) HOST=$OPTARG;; - u ) USER=$OPTARG;; - p ) PWD=$OPTARG;; - t ) TEST_ALSO=1;; - f ) OUTPUT_FILE=$OPTARG;; - h ) usage;; - * ) usage;; +while true; do + case "$1" in + -a|--action) ACTION=$2; shift 2;; + --driver) DRIVER=$2; shift 2;; + -d|--database) DATABASE=$2; shift 2;; + -h|--host) HOST=$2; shift 2;; + --port) HOST=$2; shift 2;; + -u|--user) USER=$2; shift 2;; + -p|--password) PWD=$2; shift 2;; + -t|--test) TEST_ALSO=1; shift 2;; + -f|--file) OUTPUT_FILE=$2; shift 2;; + --help) usage; shift;; + --) shift; break;; esac done - if [ -z $ACTION ]; then echo "Need to specify an action " usage fi +if [ $DRIVER != "mysql" ] && [ $DRIVER != "postgres" ]; then + echo "Only support driver or " + usage +fi + + +if [ $DRIVER == "mysql" ] && [ -z $PORT ]; then + PORT=$PORT_MYSQL +fi +if [ $DRIVER == "postgres" ] && [ -z $PORT ]; then + PORT=$PORT_POSTGRES +fi + + if [ $ACTION == "dump" ]; then DDL_FILE=`create_ddl_file` if [ -z $OUTPUT_FILE ]; then @@ -153,17 +195,31 @@ if [ $ACTION == "dump" ]; then fi fi + if [ $ACTION == "create" ]; then DDL_FILE=`create_ddl_file` echo "Applying new schema to database $DATABASE" - mysql -h $HOST -u $USER --password=$PWD < $DDL_FILE + if [ $DRIVER == "mysql" ]; then + mysql -h $HOST -P $PORT -u $USER --password=$PWD < $DDL_FILE + else + create_pgfile + psql -h $HOST -p $PORT -U $USER -d $DATABASE < $DDL_FILE + clean_pgfile + fi fi + if [ $ACTION == "clean" ]; then DDL_FILE=`create_ddl_file` CLEAN_FILE=`create_clean_file $DDL_FILE` echo "Cleaning db tables on database $DATABASE" - mysql -h $HOST -u $USER --password=$PWD < $DDL_FILE + if [ $DRIVER == "mysql" ]; then + mysql -h $HOST -P $PORT -u $USER --password=$PWD < $CLEAN_FILE + else + create_pgfile + psql -h $HOST -p $PORT -U $USER -d $DATABASE < $CLEAN_FILE + clean_pgfile + fi fi cleanup From b617a5ce40a2ac3bc5ded49f31a679e6eeee02d8 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 4 Aug 2015 17:18:18 -0700 Subject: [PATCH 061/137] Fixes #362 --- .../integration/TestIntegrationInvoice.java | 70 +++++++++++++++++++ .../killbill/billing/invoice/dao/CBADao.java | 20 +++--- 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java index a7a0b4c5a9..362c1c5bac 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java @@ -19,10 +19,13 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.UUID; import org.joda.time.DateTime; import org.joda.time.LocalDate; +import org.killbill.billing.ObjectType; import org.killbill.billing.account.api.Account; import org.killbill.billing.api.TestApiListener.NextEvent; import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck; @@ -32,9 +35,18 @@ import org.killbill.billing.invoice.api.DryRunArguments; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceItemType; +import org.killbill.billing.payment.api.Payment; +import org.killbill.billing.payment.api.PluginProperty; +import org.killbill.billing.payment.dao.PaymentTransactionModelDao; import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase; +import org.testng.Assert; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableList; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + public class TestIntegrationInvoice extends TestIntegrationBase { // @@ -187,8 +199,66 @@ public void testDryRunWithNoTargetDateAndMultipleNonAlignedSubscriptions() throw expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95"))); dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext); invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices); + } + + + @Test(groups = "slow") + public void testApplyCreditOnExistingBalance() throws Exception { + + + final int billingDay = 14; + final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone); + + log.info("Beginning test with BCD of " + billingDay); + final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay)); + + add_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT); + + // set clock to the initial start date + clock.setTime(initialCreationDate); + + createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE); + + // Move through time and verify we get the same invoice + busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE); + clock.addDays(30); + assertListenerStatus(); + + final Collection invoices = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), new LocalDate(clock.getUTCNow(), account.getTimeZone()), callContext); + assertEquals(invoices.size(), 1); + + final UUID unpaidInvoiceId = invoices.iterator().next().getId(); + final Invoice unpaidInvoice = invoiceUserApi.getInvoice(unpaidInvoiceId, callContext); + assertTrue(unpaidInvoice.getBalance().compareTo(new BigDecimal("249.95")) == 0); + + final BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(account.getId(), callContext); + assertTrue(accountBalance1.compareTo(new BigDecimal("249.95")) == 0); + + busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT); + invoiceUserApi.insertCredit(account.getId(), new BigDecimal("300"), new LocalDate(clock.getUTCNow(), account.getTimeZone()), account.getCurrency(), callContext); + assertListenerStatus(); + + final BigDecimal accountBalance2 = invoiceUserApi.getAccountBalance(account.getId(), callContext); + assertTrue(accountBalance2.compareTo(new BigDecimal("-50.05")) == 0); + + final Invoice unpaidInvoice2 = invoiceUserApi.getInvoice(unpaidInvoiceId, callContext); + assertTrue(unpaidInvoice2.getBalance().compareTo(BigDecimal.ZERO) == 0); + + remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT); + + busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT); + clock.addDays(31); + assertListenerStatus(); + + final BigDecimal accountBalance3 = invoiceUserApi.getAccountBalance(account.getId(), callContext); + assertTrue(accountBalance3.compareTo(BigDecimal.ZERO) == 0); + + final List payments = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.of(), callContext); + assertEquals(payments.size(), 1); + final Payment payment = payments.get(0); + assertTrue(payment.getPurchasedAmount().compareTo(new BigDecimal("199.90")) == 0); } } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java index 866d2484f2..00df90075d 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java @@ -79,15 +79,6 @@ public InvoiceItemModelDao computeCBAComplexity(final InvoiceModelDao invoice, f } } - // We expect a clean up to date invoice, with all the items except the CBA, that we will compute in that method - public void addCBAComplexityFromTransaction(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException { - final InvoiceItemModelDao cbaItem = computeCBAComplexity(invoice, entitySqlDaoWrapperFactory, context); - if (cbaItem != null) { - final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class); - transInvoiceItemDao.create(cbaItem, context); - } - } - // We let the code below rehydrate the invoice before we can add the CBA item public void addCBAComplexityFromTransaction(final UUID invoiceId, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException { @@ -97,6 +88,17 @@ public void addCBAComplexityFromTransaction(final UUID invoiceId, final EntitySq addCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context); } + // We expect a clean up to date invoice, with all the items except the CBA, that we will compute in that method + public void addCBAComplexityFromTransaction(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException { + final InvoiceItemModelDao cbaItem = computeCBAComplexity(invoice, entitySqlDaoWrapperFactory, context); + if (cbaItem != null) { + final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class); + transInvoiceItemDao.create(cbaItem, context); + } + List invoiceItemModelDaos = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context); + useExistingCBAFromTransaction(invoiceItemModelDaos, entitySqlDaoWrapperFactory, context); + } + public void addCBAComplexityFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException { List invoiceItemModelDaos = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context); From b1790a85d8f3e64677f309ca03758a43eb597a29 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 4 Aug 2015 17:17:31 -0700 Subject: [PATCH 062/137] Fixes #361 Conflicts: invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java --- ...FixedAndRecurringInvoiceItemGenerator.java | 21 ++++++++++--------- .../billing/invoice/dao/TestInvoiceDao.java | 12 +++++------ .../TestDefaultInvoiceGenerator.java | 4 ++-- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java index 19ceeee5ca..8f5f03fa89 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java @@ -165,20 +165,21 @@ private List processRecurringEvent(final UUID invoiceId, final UUID if (rate != null) { final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency); + if (BigDecimal.ZERO.compareTo(amount) != 0) { + final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, + accountId, + thisEvent.getSubscription().getBundleId(), + thisEvent.getSubscription().getId(), + thisEvent.getPlan().getName(), + thisEvent.getPlanPhase().getName(), + itemDatum.getStartDate(), itemDatum.getEndDate(), + amount, rate, currency); + items.add(recurringItem); - final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, - accountId, - thisEvent.getSubscription().getBundleId(), - thisEvent.getSubscription().getId(), - thisEvent.getPlan().getName(), - thisEvent.getPlanPhase().getName(), - itemDatum.getStartDate(), itemDatum.getEndDate(), - amount, rate, currency); - items.add(recurringItem); + } } } updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate); - } } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java index e1ad03a796..0823824f80 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java @@ -1149,15 +1149,15 @@ public void testInvoiceGenerationForImmediateChanges() throws InvoiceApiExceptio public void testInvoiceForFreeTrial() throws InvoiceApiException, CatalogApiException { final Currency currency = Currency.USD; final DefaultPrice price = new DefaultPrice(BigDecimal.ZERO, Currency.USD); - final MockInternationalPrice recurringPrice = new MockInternationalPrice(price); - final MockPlanPhase phase = new MockPlanPhase(recurringPrice, null); + final MockInternationalPrice fixedPrice = new MockInternationalPrice(price); + final MockPlanPhase phase = new MockPlanPhase(null, fixedPrice); final MockPlan plan = new MockPlan(phase); final SubscriptionBase subscription = getZombieSubscription(); final DateTime effectiveDate = invoiceUtil.buildDate(2011, 1, 1).toDateTimeAtStartOfDay(); - final BillingEvent event = invoiceUtil.createMockBillingEvent(null, subscription, effectiveDate, plan, phase, null, - recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 15, BillingMode.IN_ADVANCE, + final BillingEvent event = invoiceUtil.createMockBillingEvent(null, subscription, effectiveDate, plan, phase, + fixedPrice.getPrice(currency), null, currency, BillingPeriod.MONTHLY, 15, BillingMode.IN_ADVANCE, "testEvent", 1L, SubscriptionBaseTransitionType.CREATE); final BillingEventSet events = new MockBillingEventSet(); events.add(event); @@ -1165,10 +1165,8 @@ public void testInvoiceForFreeTrial() throws InvoiceApiException, CatalogApiExce final LocalDate targetDate = invoiceUtil.buildDate(2011, 1, 15); final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, context); final Invoice invoice = invoiceWithMetadata.getInvoice(); + assertNotNull(invoice); - // expect one pro-ration item and one full-period item - assertEquals(invoice.getNumberOfItems(), 2); - assertEquals(invoice.getBalance().compareTo(ZERO), 0); } private SubscriptionBase getZombieSubscription(UUID subscriptionId) { diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java index 4d45ea45ac..4882cc2155 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java @@ -561,13 +561,13 @@ public void testZeroDollarEvents() throws InvoiceApiException, CatalogApiExcepti final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext); final Invoice invoice = invoiceWithMetadata.getInvoice(); - assertEquals(invoice.getNumberOfItems(), 1); + assertNull(invoice); } @Test(groups = "fast") public void testEndDateIsCorrect() throws InvoiceApiException, CatalogApiException { final Plan plan = new MockPlan(); - final PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO); + final PlanPhase planPhase = createMockMonthlyPlanPhase(ONE); final BillingEventSet events = new MockBillingEventSet(); final LocalDate startDate = clock.getUTCToday().minusDays(1); final LocalDate targetDate = startDate.plusDays(1); From 30981200e85c2df922f4e1bd475e5df657c4175a Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 8 Sep 2015 17:15:53 -0700 Subject: [PATCH 063/137] jaxrs: Remove unsued extra parameter to buildComboFormDescriptor --- .../killbill/billing/jaxrs/resources/PaymentGatewayResource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java index 62b723e852..11a622125b 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java @@ -94,7 +94,6 @@ public PaymentGatewayResource(final JaxrsUriBuilder uriBuilder, @ApiOperation(value = "Combo API to generate form data to redirect the customer to the gateway", response = HostedPaymentPageFormDescriptorJson.class) @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid data for Account or PaymentMethod")}) public Response buildComboFormDescriptor(final ComboHostedPaymentPageJson json, - @PathParam("accountId") final String accountIdString, @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List paymentControlPluginNames, @QueryParam(QUERY_PLUGIN_PROPERTY) final List pluginPropertiesString, @HeaderParam(HDR_CREATED_BY) final String createdBy, From 45aebae3c3dd975b8e31cacdb30e0ecd1b973f14 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 8 Sep 2015 17:52:43 -0700 Subject: [PATCH 064/137] payment: Fixes #374 Change behavior of janitor task IncompletePaymentTransactionTask to transition an UNKNOWN entry into PLUGIN_FAILURE when the plugin returns a CANCEL state. --- .../core/janitor/IncompletePaymentTransactionTask.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java index 94d318b0c2..8133904bd8 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java @@ -186,12 +186,11 @@ private boolean updatePaymentAndTransactionInternal(final PaymentModelDao paymen newPaymentState = paymentStateMachineHelper.getFailureStateForTransaction(paymentTransaction.getTransactionType()); break; case PLUGIN_FAILURE: + newPaymentState = paymentStateMachineHelper.getErroredStateForTransaction(paymentTransaction.getTransactionType()); + break; case UNKNOWN: default: - if (transactionStatus == paymentTransaction.getTransactionStatus()) { - log.debug("Janitor IncompletePaymentTransactionTask unable to repair payment {}, transaction {}: {} -> {}", - payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); - } else { + if (transactionStatus != paymentTransaction.getTransactionStatus()) { log.info("Janitor IncompletePaymentTransactionTask unable to repair payment {}, transaction {}: {} -> {}", payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus); } From 96f1342e2df942161160c7def5b1e702c59f775e Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 12:21:51 -0700 Subject: [PATCH 065/137] jaxrs: Add ability to retrieve all tags for an account (and option to filter by objectType). Fixes #392 --- .../jaxrs/resources/AccountResource.java | 22 +++++++++++ .../jaxrs/resources/JaxRsResourceBase.java | 6 ++- .../jaxrs/resources/JaxrsResource.java | 4 ++ pom.xml | 2 +- .../org/killbill/billing/jaxrs/TestTag.java | 38 +++++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java index 29364d05a8..374fb62611 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java @@ -100,6 +100,7 @@ import org.killbill.billing.util.config.PaymentConfig; import org.killbill.billing.util.entity.Pagination; import org.killbill.billing.util.tag.ControlTagType; +import org.killbill.billing.util.tag.Tag; import org.killbill.clock.Clock; import com.codahale.metrics.annotation.Timed; @@ -891,6 +892,27 @@ public Response getTags(@PathParam(ID_PARAM_NAME) final String accountIdString, return super.getTags(accountId, accountId, auditMode, includedDeleted, context.createContext(request)); } + @Timed + @GET + @Path("/{accountId:" + UUID_PATTERN + "}/" + ALL_TAGS) + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Retrieve account tags", response = TagJson.class, responseContainer = "List") + @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied"), + @ApiResponse(code = 404, message = "Account not found")}) + public Response getAllTags(@PathParam(ID_PARAM_NAME) final String accountIdString, + @QueryParam(QUERY_OBJECT_TYPE) final ObjectType objectType, + @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode, + @QueryParam(QUERY_TAGS_INCLUDED_DELETED) @DefaultValue("false") final Boolean includedDeleted, + @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException { + final UUID accountId = UUID.fromString(accountIdString); + final TenantContext tenantContext = context.createContext(request); + final List tags = objectType != null ? + tagUserApi.getTagsForAccountType(accountId, objectType, includedDeleted, tenantContext) : + tagUserApi.getTagsForAccount(accountId, includedDeleted, tenantContext); + return createTagResponse(accountId, tags, auditMode, tenantContext); + } + + @Timed @POST @Path("/{accountId:" + UUID_PATTERN + "}/" + TAGS) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java index 29218826ec..b46b836bdf 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java @@ -140,6 +140,10 @@ protected ObjectType getObjectType() { protected Response getTags(final UUID accountId, final UUID taggedObjectId, final AuditMode auditMode, final boolean includeDeleted, final TenantContext context) throws TagDefinitionApiException { final List tags = tagUserApi.getTagsForObject(taggedObjectId, getObjectType(), includeDeleted, context); + return createTagResponse(accountId, tags, auditMode, context); + } + + protected Response createTagResponse(final UUID accountId, final List tags, final AuditMode auditMode, final TenantContext context) throws TagDefinitionApiException { final AccountAuditLogsForObjectType tagsAuditLogs = auditUserApi.getAccountAuditLogs(accountId, ObjectType.TAG, auditMode.getLevel(), context); final Map tagDefinitionsCache = new HashMap(); @@ -153,10 +157,10 @@ protected Response getTags(final UUID accountId, final UUID taggedObjectId, fina final List auditLogs = tagsAuditLogs.getAuditLogs(tag.getId()); result.add(new TagJson(tag, tagDefinition, auditLogs)); } - return Response.status(Response.Status.OK).entity(result).build(); } + protected Response createTags(final UUID id, final String tagList, final UriInfo uriInfo, diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java index d29d52774e..076fd5503f 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java @@ -88,10 +88,13 @@ public interface JaxrsResource { public static final String QUERY_PAYMENT_METHOD_ID = "paymentMethodId"; public static final String QUERY_PAYMENT_CONTROL_PLUGIN_NAME = "controlPluginName"; + public static final String QUERY_TAGS = "tagList"; public static final String QUERY_TAGS_INCLUDED_DELETED = "includedDeleted"; public static final String QUERY_CUSTOM_FIELDS = "customFieldList"; + public static final String QUERY_OBJECT_TYPE = "objectType"; + public static final String QUERY_PAYMENT_METHOD_PLUGIN_NAME = "pluginName"; public static final String QUERY_WITH_PLUGIN_INFO = "withPluginInfo"; public static final String QUERY_PAYMENT_METHOD_IS_DEFAULT = "isDefault"; @@ -172,6 +175,7 @@ public interface JaxrsResource { public static final String CHARGEBACKS = "chargebacks"; public static final String CHARGEBACKS_PATH = PREFIX + "/" + CHARGEBACKS; + public static final String ALL_TAGS = "allTags"; public static final String TAGS = "tags"; public static final String TAGS_PATH = PREFIX + "/" + TAGS; diff --git a/pom.xml b/pom.xml index 6abdc1f69f..c3dd12271a 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.39 + 0.40 killbill 0.15.4-SNAPSHOT diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java index a2641298d1..725ddac408 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java @@ -23,12 +23,17 @@ import javax.annotation.Nullable; +import org.joda.time.DateTime; import org.killbill.billing.ObjectType; +import org.killbill.billing.catalog.api.BillingPeriod; +import org.killbill.billing.catalog.api.ProductCategory; import org.killbill.billing.client.KillBillClientException; import org.killbill.billing.client.model.Account; +import org.killbill.billing.client.model.Subscription; import org.killbill.billing.client.model.Tag; import org.killbill.billing.client.model.TagDefinition; import org.killbill.billing.client.model.Tags; +import org.killbill.billing.util.api.AuditLevel; import org.killbill.billing.util.tag.ControlTagType; import org.testng.Assert; import org.testng.annotations.Test; @@ -94,6 +99,39 @@ public void testMultipleTagDefinitionOk() throws Exception { assertEquals(objFromJson.size(), 3 + sizeSystemTag); } + + @Test(groups = "slow", description = "Can search all tags for an account") + public void testGetAllTagsByType() throws Exception { + + final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0); + clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis()); + + final Account account = createAccountWithDefaultPaymentMethod(); + + final Subscription subscriptionJson = createEntitlement(account.getAccountId(), "87544332", "Shotgun", + ProductCategory.BASE, BillingPeriod.MONTHLY, true); + + for (final ControlTagType controlTagType : ControlTagType.values()) { + killBillClient.createAccountTag(account.getAccountId(), controlTagType.getId(), createdBy, reason, comment); + } + + final TagDefinition bundleTagDefInput = new TagDefinition(null, false, "bundleTagDef", "nothing special", ImmutableList.of()); + final TagDefinition bundleTagDef = killBillClient.createTagDefinition(bundleTagDefInput, createdBy, reason, comment); + + killBillClient.createBundleTag(subscriptionJson.getBundleId(), bundleTagDef.getId(), createdBy, reason, comment); + + final Tags allBundleTags = killBillClient.getBundleTags(subscriptionJson.getBundleId(), AuditLevel.FULL); + Assert.assertEquals(allBundleTags.size(), 1); + + final Tags allAccountTags = killBillClient.getAllAccountTags(account.getAccountId(), null, AuditLevel.FULL); + Assert.assertEquals(allAccountTags.size(), ControlTagType.values().length + 1); + + + final Tags allBundleTagsForAccount = killBillClient.getAllAccountTags(account.getAccountId(), ObjectType.BUNDLE.name(), AuditLevel.FULL); + Assert.assertEquals(allBundleTagsForAccount.size(), 1); + } + + @Test(groups = "slow", description = "Can search system tags") public void testSystemTagsPagination() throws Exception { final Account account = createAccount(); From 4f8a23f35bdd9ce9a291a0cd2adb1466b6a3ad8b Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 13:57:40 -0700 Subject: [PATCH 066/137] payment: Fixes #378 Removed unsued fields or setters from PaymentStateContext. Documented the fields and when/why they get updated Cleanup (removed protected filed attribute,...) --- .../payment/core/sm/PaymentStateContext.java | 95 ++++++++++++------- .../sm/control/DefaultControlInitiated.java | 2 +- .../control/PaymentStateControlContext.java | 13 +-- .../PaymentEnteringStateCallback.java | 6 -- .../core/sm/payments/PaymentOperation.java | 4 - .../MockRetryAuthorizeOperationCallback.java | 18 ++-- .../sm/TestPaymentLeavingStateCallback.java | 4 +- 7 files changed, 72 insertions(+), 70 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java index cbe72e7172..8975b2256e 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java @@ -37,31 +37,40 @@ public class PaymentStateContext { - // HACK - protected UUID paymentMethodId; - protected UUID attemptId; - - // Stateful objects created by the callbacks and passed to the other following callbacks in the automaton - protected List onLeavingStateExistingTransactions; - protected PaymentTransactionModelDao paymentTransactionModelDao; - protected PaymentTransactionInfoPlugin paymentTransactionInfoPlugin; - protected BigDecimal amount; - protected String paymentExternalKey; - protected String paymentTransactionExternalKey; - protected Currency currency; - protected Iterable properties; - protected boolean skipOperationForUnknownTransaction; - - // Can be updated later via paymentTransactionModelDao (e.g. for auth or purchase) - protected final UUID paymentId; - protected final UUID transactionId; - protected final Account account; - protected final TransactionType transactionType; - protected final boolean shouldLockAccountAndDispatch; - protected final InternalCallContext internalCallContext; - protected final CallContext callContext; - protected final boolean isApiPayment; - protected final OperationResult overridePluginOperationResult; + + // The following fields (paymentId, transactionId, amount, currency) may take their value from the paymentTransactionModelDao *when they are not already set* + private PaymentTransactionModelDao paymentTransactionModelDao; + // Initialized in CTOR or only set through paymentTransactionModelDao + private UUID paymentId; + private UUID transactionId; + + // Can be overriden by control plugin + private BigDecimal amount; + private Currency currency; + private UUID paymentMethodId; + private Iterable properties; + + // Set in the doOperationCallback when coming back from payment plugin + private PaymentTransactionInfoPlugin paymentTransactionInfoPlugin; + + // Set in the control layer in the leavingState callback + private String paymentExternalKey; + private String paymentTransactionExternalKey; + + // Set in the control layer after creating the attempt in the enteringState callback + private UUID attemptId; + + // This is purely a performance improvement to avoid fetching the existing transactions for that payment throughout the state machine + private List onLeavingStateExistingTransactions; + + // Immutable + private final Account account; + private final TransactionType transactionType; + private final boolean shouldLockAccountAndDispatch; + private final OperationResult overridePluginOperationResult; + private final InternalCallContext internalCallContext; + private final CallContext callContext; + private final boolean isApiPayment; // Use to create new transactions only public PaymentStateContext(final boolean isApiPayment, @Nullable final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final TransactionType transactionType, @@ -95,7 +104,6 @@ public PaymentStateContext(final boolean isApiPayment, @Nullable final UUID paym this.internalCallContext = internalCallContext; this.callContext = callContext; this.onLeavingStateExistingTransactions = ImmutableList.of(); - this.skipOperationForUnknownTransaction = false; } public boolean isApiPayment() { @@ -112,6 +120,18 @@ public PaymentTransactionModelDao getPaymentTransactionModelDao() { public void setPaymentTransactionModelDao(final PaymentTransactionModelDao paymentTransactionModelDao) { this.paymentTransactionModelDao = paymentTransactionModelDao; + if (paymentId == null) { + this.paymentId = paymentTransactionModelDao.getPaymentId(); + } + if (transactionId == null) { + this.transactionId = paymentTransactionModelDao.getId(); + } + if (amount == null) { + this.amount = paymentTransactionModelDao.getAmount(); + } + if (currency == null) { + this.currency = paymentTransactionModelDao.getCurrency(); + } } public List getOnLeavingStateExistingTransactions() { @@ -131,11 +151,11 @@ public void setPaymentTransactionInfoPlugin(final PaymentTransactionInfoPlugin p } public UUID getPaymentId() { - return paymentId != null ? paymentId : (paymentTransactionModelDao != null ? paymentTransactionModelDao.getPaymentId() : null); + return paymentId; } public UUID getTransactionId() { - return transactionId != null ? transactionId : (paymentTransactionModelDao != null ? paymentTransactionModelDao.getId() : null); + return transactionId; } public String getPaymentExternalKey() { @@ -171,13 +191,11 @@ public void setAttemptId(final UUID attemptId) { } public BigDecimal getAmount() { - // For a complete operation, if the amount isn't specified, take the original amount on the transaction - return amount == null && paymentTransactionModelDao != null ? paymentTransactionModelDao.getAmount() : amount; + return amount; } public Currency getCurrency() { - // For a complete operation, if the currency isn't specified, take the original currency on the transaction - return currency == null && paymentTransactionModelDao != null ? paymentTransactionModelDao.getCurrency() : currency; + return currency; } public TransactionType getTransactionType() { @@ -204,11 +222,16 @@ public CallContext getCallContext() { return callContext; } - public boolean isSkipOperationForUnknownTransaction() { - return skipOperationForUnknownTransaction; + public void setAmount(final BigDecimal adjustedAmount) { + this.amount = adjustedAmount; + } + + public void setCurrency(final Currency currency) { + this.currency = currency; } - public void setSkipOperationForUnknownTransaction(final boolean skipOperationForUnknownTransaction) { - this.skipOperationForUnknownTransaction = skipOperationForUnknownTransaction; + public void setProperties(final Iterable properties) { + this.properties = properties; } + } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlInitiated.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlInitiated.java index 54783cba7d..4ead401df3 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlInitiated.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlInitiated.java @@ -79,7 +79,7 @@ public void leavingState(final State state) throws OperationException { // // We don't serialize any properties at this stage to avoid serializing sensitive information. // However, if after going through the control plugins, the attempt end up in RETRIED state, - // the properties will be serialized in the enteringState( callback (any plugin that sets a + // the properties will be serialized in the enteringState callback (any plugin that sets a // retried date is responsible to correctly remove sensitive information such as CVV, ...) // final byte[] serializedProperties = PluginPropertySerializer.serialize(ImmutableList.of()); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java index d9af3a922b..8b51735552 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java @@ -72,17 +72,6 @@ public void setResult(final Payment result) { this.result = result; } - public void setAmount(final BigDecimal adjustedAmount) { - this.amount = adjustedAmount; - } - - public void setCurrency(final Currency currency) { - this.currency = currency; - } - - public void setProperties(final Iterable properties) { - this.properties = properties; - } public PaymentTransaction getCurrentTransaction() { if (result == null || result.getTransactions() == null) { @@ -91,7 +80,7 @@ public PaymentTransaction getCurrentTransaction() { return Iterables.tryFind(result.getTransactions(), new Predicate() { @Override public boolean apply(final PaymentTransaction input) { - return ((DefaultPaymentTransaction) input).getAttemptId().equals(attemptId); + return ((DefaultPaymentTransaction) input).getAttemptId().equals(getAttemptId()); } }).orNull(); } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentEnteringStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentEnteringStateCallback.java index 9860f6b828..f5dd2f2898 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentEnteringStateCallback.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentEnteringStateCallback.java @@ -17,8 +17,6 @@ package org.killbill.billing.payment.core.sm.payments; -import javax.annotation.Nullable; - import org.killbill.automaton.Operation; import org.killbill.automaton.OperationResult; import org.killbill.automaton.State; @@ -52,10 +50,6 @@ protected PaymentEnteringStateCallback(final PaymentAutomatonDAOHelper daoHelper public void enteringState(final State newState, final Operation.OperationCallback operationCallback, final OperationResult operationResult, final LeavingStateCallback leavingStateCallback) { logger.debug("Entering state {} with result {}", newState.getName(), operationResult); - if (paymentStateContext.isSkipOperationForUnknownTransaction()) { - return; - } - // If the transaction was not created -- for instance we had an exception in leavingState callback then we bail; if not, then update state: if (paymentStateContext.getPaymentTransactionModelDao() != null && paymentStateContext.getPaymentTransactionModelDao().getId() != null) { final PaymentTransactionInfoPlugin paymentInfoPlugin = paymentStateContext.getPaymentTransactionInfoPlugin(); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java index 489640938f..7d31cbbfb2 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java @@ -69,10 +69,6 @@ protected PaymentOperation(final GlobalLocker locker, @Override public OperationResult doOperationCallback() throws OperationException { - if (paymentStateContext.isSkipOperationForUnknownTransaction()) { - return OperationResult.SUCCESS; - } - try { this.plugin = daoHelper.getPaymentProviderPlugin(); diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java index 1a8f812d44..2a3a677999 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java @@ -71,23 +71,23 @@ protected Payment doCallSpecificOperationCallback() throws PaymentApiException { } final PaymentModelDao payment = new PaymentModelDao(clock.getUTCNow(), clock.getUTCNow(), - paymentStateContext.account.getId(), - paymentStateContext.paymentMethodId, - paymentStateContext.paymentExternalKey); + paymentStateContext.getAccount().getId(), + paymentStateContext.getPaymentMethodId(), + paymentStateContext.getPaymentExternalKey()); final PaymentTransactionModelDao transaction = new PaymentTransactionModelDao(clock.getUTCNow(), clock.getUTCNow(), paymentStateContext.getAttemptId(), - paymentStateContext.paymentTransactionExternalKey, - paymentStateContext.paymentId, - paymentStateContext.transactionType, + paymentStateContext.getPaymentTransactionExternalKey(), + paymentStateContext.getPaymentId(), + paymentStateContext.getTransactionType(), clock.getUTCNow(), TransactionStatus.SUCCESS, - paymentStateContext.amount, - paymentStateContext.currency, + paymentStateContext.getAmount(), + paymentStateContext.getCurrency(), "", ""); - final PaymentModelDao paymentModelDao = paymentDao.insertPaymentWithFirstTransaction(payment, transaction, paymentStateContext.internalCallContext); + final PaymentModelDao paymentModelDao = paymentDao.insertPaymentWithFirstTransaction(payment, transaction, paymentStateContext.getInternalCallContext()); final PaymentTransaction convertedTransaction = new DefaultPaymentTransaction(transaction.getId(), paymentStateContext.getAttemptId(), transaction.getTransactionExternalKey(), diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java index 36800f1bb4..1717dc49c3 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java @@ -134,8 +134,8 @@ public void testLeaveStateForConflictingPaymentTransactionExternalKeyAcrossAccou paymentStateContext.getPaymentMethodId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency(), - paymentStateContext.shouldLockAccountAndDispatch, - paymentStateContext.overridePluginOperationResult, + paymentStateContext.shouldLockAccountAndDispatch(), + paymentStateContext.getOverridePluginOperationResult(), paymentStateContext.getProperties(), internalCallContextForOtherAccount, callContext); From 159ea86613628789452ce4c703b2f4bc0758ce11 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 14:35:25 -0700 Subject: [PATCH 067/137] jaxrs: Fixes #289 Add filtering logic to make sure we don't return dupliacte payment associated with a given invoice. --- .../billing/jaxrs/resources/InvoiceResource.java | 14 ++++++++++++-- .../killbill/billing/jaxrs/TestInvoicePayment.java | 7 +++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java index d45a84b631..461eff62a6 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java @@ -111,6 +111,7 @@ import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.inject.Inject; @@ -522,9 +523,18 @@ public Response getPayments(@PathParam("invoiceId") final String invoiceId, final TenantContext tenantContext = context.createContext(request); final Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId), tenantContext); + + // Extract unique set of paymentId for this invoice + final Set invoicePaymentIds = ImmutableSet.copyOf(Iterables.transform(invoice.getPayments(), new Function() { + @Override + public UUID apply(final InvoicePayment input) { + return input.getPaymentId(); + } + })); + final List payments = new ArrayList(); - for (InvoicePayment cur : invoice.getPayments()) { - final Payment payment = paymentApi.getPayment(cur.getPaymentId(), withPluginInfo, ImmutableList.of(), tenantContext); + for (final UUID paymentId : invoicePaymentIds) { + final Payment payment = paymentApi.getPayment(paymentId, withPluginInfo, ImmutableList.of(), tenantContext); payments.add(payment); } final List result = new ArrayList(payments.size()); diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java index 880b81c3bf..bf11eaab0d 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java @@ -96,6 +96,9 @@ public void testFullRefundWithInvoiceAdjustment() throws Exception { final BigDecimal refundAmount = paymentJson.getPurchasedAmount(); final BigDecimal expectedInvoiceBalance = BigDecimal.ZERO; + final InvoicePayments invoicePayments = killBillClient.getInvoicePayment(paymentJson.getTargetInvoiceId()); + Assert.assertEquals(invoicePayments.size(), 1); + // Post and verify the refund final InvoicePaymentTransaction refund = new InvoicePaymentTransaction(); refund.setPaymentId(paymentJson.getPaymentId()); @@ -106,6 +109,10 @@ public void testFullRefundWithInvoiceAdjustment() throws Exception { // Verify the invoice balance verifyInvoice(paymentJson, expectedInvoiceBalance); + + final InvoicePayments invoicePaymentsAfterRefund = killBillClient.getInvoicePayment(paymentJson.getTargetInvoiceId()); + Assert.assertEquals(invoicePaymentsAfterRefund.size(), 1); + } @Test(groups = "slow", description = "Can create a partial refund with invoice adjustment") From a64e8de6319d106daf4ef8554ecfdb2d8b52aa8b Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 15:15:05 -0700 Subject: [PATCH 068/137] beatrix: Minor tweak in existing test (no change of behavior) --- .../usage/TestConsumableInArrear.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java index c7a909df5c..6713be18ce 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java @@ -18,7 +18,6 @@ import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.UUID; @@ -78,8 +77,7 @@ public void testWithNoUsageInPeriodAndOldUsage() throws Exception { setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext); busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT); - clock.setDay(new LocalDate(2012, 5, 1)); - + clock.addDays(30); assertListenerStatus(); invoiceChecker.checkInvoice(account.getId(), 2, callContext, @@ -87,20 +85,19 @@ public void testWithNoUsageInPeriodAndOldUsage() throws Exception { new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90"))); // We don't expect any invoice, but we want to give the system the time to verify there is nothing to do so we can fail - clock.setDay(new LocalDate(2012, 6, 1)); + clock.addMonths(1); Thread.sleep(1000); setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 1), 50L, callContext); setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 16), 300L, callContext); busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT); - clock.setDay(new LocalDate(2012, 7, 1)); + clock.addMonths(1); assertListenerStatus(); invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.USAGE, new BigDecimal("11.80"))); - // Should be ignored because this is outside of optimization range (org.killbill.invoice.readMaxRawUsagePreviousPeriod = 2) => we will only look for items > 2012-7-1 - 2 months = 2012-5-1 setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 30), 100L, callContext); @@ -112,7 +109,7 @@ public void testWithNoUsageInPeriodAndOldUsage() throws Exception { setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 7, 16), 300L, callContext); busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT); - clock.setDay(new LocalDate(2012, 8, 1)); + clock.addMonths(1); assertListenerStatus(); invoiceChecker.checkInvoice(account.getId(), 4, callContext, @@ -120,7 +117,6 @@ public void testWithNoUsageInPeriodAndOldUsage() throws Exception { new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 1), new LocalDate(2012, 8, 1), InvoiceItemType.USAGE, new BigDecimal("11.80"))); } - @Test(groups = "slow") public void testWithCancellation() throws Exception { @@ -149,20 +145,19 @@ public void testWithCancellation() throws Exception { setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext); busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT); - clock.setDay(new LocalDate(2012, 5, 1)); + clock.addDays(30); assertListenerStatus(); invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")), new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90"))); - setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 3), 99L, callContext); setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 5), 100L, callContext); // This one should be ignored setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 29), 100L, callContext); - clock.setDay(new LocalDate(2012, 5, 28)); + clock.addDays(27); busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE, NextEvent.PAYMENT); aoSubscription.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(2012, 5, 28), BillingActionPolicy.IMMEDIATE, ImmutableList.of(), callContext); assertListenerStatus(); @@ -170,9 +165,7 @@ public void testWithCancellation() throws Exception { invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 28), InvoiceItemType.USAGE, new BigDecimal("5.90"))); - busHandler.pushExpectedEvents(); - clock.setDay(new LocalDate(2012, 6, 1)); - + clock.addDays(4); Thread.sleep(1000); } From 557b8b549db884fd71abc5195e0a50476e05999e Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 15:58:07 -0700 Subject: [PATCH 069/137] Add postgresql in the allow_failures block from travis config (see #382) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e8847be803..e34717fb69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ env: matrix: allow_failures: - jdk: oraclejdk8 + - env: PHASE="-Ppostgresql" include: - env: PHASE="-Pmysql" jdk: openjdk7 From 33acb8866f389a3892144d1b5f580899f238f773 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 16:57:31 -0700 Subject: [PATCH 070/137] subscription: Fixes #387 --- .../DefaultSubscriptionBaseApiService.java | 3 ++ .../api/user/TestUserApiChangePlan.java | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java index 5ff0345d7e..2e829f30f7 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java @@ -354,6 +354,9 @@ private DateTime doChangePlan(final DefaultSubscriptionBase subscription, final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, context); final Plan newPlan = catalogService.getFullCatalog(internalCallContext).createOrFindPlan(newProductName, newBillingPeriod, newPriceList, overridesWithContext, effectiveDate, subscription.getStartDate()); + if (newPlan.getProduct().getCategory() != subscription.getCategory()) { + throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_INVALID, subscription.getId()); + } final List changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, now, effectiveDate, now, false, internalCallContext); dao.changePlan(subscription, changeEvents, internalCallContext); subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), catalogService.getFullCatalog(internalCallContext)); diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java index 35f6bed749..f3418f9eed 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java @@ -21,6 +21,8 @@ import org.joda.time.DateTime; import org.joda.time.Interval; +import org.killbill.billing.ErrorCode; +import org.killbill.billing.catalog.api.PlanAlignmentCreate; import org.testng.Assert; import org.testng.annotations.Test; @@ -427,4 +429,32 @@ public void testCorrectPhaseAlignmentOnChange() throws SubscriptionBaseApiExcept assertListenerStatus(); } + + @Test(groups = "slow") + public void testInvalidChangesAcrossProductTypes() throws SubscriptionBaseApiException { + final String baseProduct = "Shotgun"; + final BillingPeriod baseTerm = BillingPeriod.MONTHLY; + final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME; + + // CREATE BP + final DefaultSubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList); + + // MOVE CLOCK 14 DAYS LATER + Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(14)); + clock.addDeltaFromReality(it.toDurationMillis()); + + // Create AO + final String aoProduct = "Laser-Scope"; + final BillingPeriod aoTerm = BillingPeriod.MONTHLY; + final String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME; + DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, aoProduct, aoTerm, aoPriceList); + + try { + aoSubscription.changePlanWithDate(baseProduct, baseTerm, basePriceList, null, clock.getUTCNow(), callContext); + Assert.fail("Should not allow plan change across product type"); + } catch (final SubscriptionBaseApiException e) { + Assert.assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_INVALID.getCode()); + } + } + } From aeaf855a0eb0b265cb5ace05e9e7c403ab460b72 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 17:28:13 -0700 Subject: [PATCH 071/137] Error code cleanup --- .../SubscriptionRepairExceptionMapper.java | 30 +------------------ .../DefaultSubscriptionBaseTimelineApi.java | 5 +--- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/SubscriptionRepairExceptionMapper.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/SubscriptionRepairExceptionMapper.java index 68f99d762a..1edf4e0bc3 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/SubscriptionRepairExceptionMapper.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/SubscriptionRepairExceptionMapper.java @@ -38,35 +38,7 @@ public SubscriptionRepairExceptionMapper(@Context final UriInfo uriInfo) { @Override public Response toResponse(final SubscriptionBaseRepairException exception) { - if (exception.getCode() == ErrorCode.SUB_REPAIR_AO_CREATE_BEFORE_BP_START.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_BP_RECREATE_MISSING_AO.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_BP_RECREATE_MISSING_AO_CREATE.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_INVALID_DELETE_SET.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_MISSING_AO_DELETE_EVENT.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_NO_ACTIVE_SUBSCRIPTIONS.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_NON_EXISTENT_DELETE_EVENT.getCode()) { - return buildNotFoundResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_SUB_EMPTY.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_SUB_RECREATE_NOT_EMPTY.getCode()) { - return buildBadRequestResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_UNKNOWN_BUNDLE.getCode()) { - return buildNotFoundResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_UNKNOWN_SUBSCRIPTION.getCode()) { - return buildNotFoundResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_UNKNOWN_TYPE.getCode()) { - return buildNotFoundResponse(exception, uriInfo); - } else if (exception.getCode() == ErrorCode.SUB_REPAIR_VIEW_CHANGED.getCode()) { + if (exception.getCode() == ErrorCode.SUB_NO_ACTIVE_SUBSCRIPTIONS.getCode()) { return buildBadRequestResponse(exception, uriInfo); } else { return fallback(exception, uriInfo); diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java index 065830290e..4dca0a1ff5 100644 --- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java +++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java @@ -63,15 +63,12 @@ public DefaultSubscriptionBaseTimelineApi(final CatalogService catalogService, public BundleBaseTimeline getBundleTimeline(final SubscriptionBaseBundle bundle, final TenantContext context) throws SubscriptionBaseRepairException { try { - if (bundle == null) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_UNKNOWN_BUNDLE, bundle.getExternalKey()); - } final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(bundle.getAccountId(), context); final List subscriptions = dao.getSubscriptions(bundle.getId(), ImmutableList.of(), internalTenantContext); if (subscriptions.size() == 0) { - throw new SubscriptionBaseRepairException(ErrorCode.SUB_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, bundle.getId()); + throw new SubscriptionBaseRepairException(ErrorCode.SUB_NO_ACTIVE_SUBSCRIPTIONS, bundle.getId()); } final String viewId = getViewId(((DefaultSubscriptionBaseBundle) bundle).getLastSysUpdateDate(), subscriptions); final List repairs = createGetSubscriptionRepairList(subscriptions, Collections.emptyList(), internalTenantContext); From 9522fc2568912b0416323cc91565bb10d6e3e8ee Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 17:34:52 -0700 Subject: [PATCH 072/137] Update killbill-api (with ErrorCode cleanup) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3dd12271a..a731cc5cff 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.40 + 0.41 killbill 0.15.4-SNAPSHOT From e41be3495fe1ccd9e39e976d8506c605b147fe65 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 9 Sep 2015 17:36:24 -0700 Subject: [PATCH 073/137] Remove postgresql build until known issue has been fixed (previous commit 557b8b549db884fd71abc5195e0a50476e05999e for allow_failures did not work, must have the syntax wrong) --- .travis.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index e34717fb69..dfb5cdc785 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,6 @@ env: matrix: allow_failures: - jdk: oraclejdk8 - - env: PHASE="-Ppostgresql" include: - env: PHASE="-Pmysql" jdk: openjdk7 @@ -33,16 +32,6 @@ matrix: jdk: oraclejdk7 - env: PHASE="-Pmysql,jdk18" jdk: oraclejdk8 - - env: PHASE="-Ppostgresql" - jdk: openjdk7 - - env: PHASE="-Ppostgresql" - jdk: oraclejdk7 - - env: PHASE="-Ppostgresql,jdk17" - jdk: openjdk7 - - env: PHASE="-Ppostgresql,jdk17" - jdk: oraclejdk7 - - env: PHASE="-Ppostgresql,jdk18" - jdk: oraclejdk8 - env: PHASE="-Ptravis" jdk: openjdk7 - env: PHASE="-Ptravis" From f0e5088c543118bf338dd1b854821a255128d141 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 10 Sep 2015 13:47:59 -0700 Subject: [PATCH 074/137] jaxrs: Remove constraint to pass currency when creating external charge. Fixes #388 The code will check all the input external charge items for any currency passed and throw if this is different than the account currency. If it is not set it will set it with the account currency. Added a test to verify the new behavior. --- .../jaxrs/resources/InvoiceResource.java | 51 +++++++++++++------ pom.xml | 2 +- .../killbill/billing/jaxrs/TestInvoice.java | 37 ++++++++++++++ 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java index 461eff62a6..a7dbf1f875 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java @@ -459,25 +459,12 @@ public Response createExternalCharges(final Iterable externalCh final CallContext callContext = context.createContext(createdBy, reason, comment, request); final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), callContext); - - // TODO Get rid of that check once we truly support multiple currencies per account - // See discussion https://github.com/killbill/killbill/commit/942e214d49e9c7ed89da76d972ee017d2d3ade58#commitcomment-6045547 - final Set currencies = new HashSet(Lists.transform(ImmutableList.copyOf(externalChargesJson), - new Function() { - @Override - public Currency apply(final InvoiceItemJson input) { - return input.getCurrency(); - } - } - )); - if (currencies.size() != 1 || !currencies.iterator().next().equals(account.getCurrency())) { - throw new InvoiceApiException(ErrorCode.CURRENCY_INVALID, currencies.iterator().next(), account.getCurrency()); - } + final Iterable sanitizedExternalChargesJson = cloneRefundItemsWithValidCurrency(account.getCurrency(), externalChargesJson); // Get the effective date of the external charge, in the account timezone final LocalDate requestedDate = toLocalDate(account, requestedDateTimeString, callContext); - final Iterable externalCharges = Iterables.transform(externalChargesJson, + final Iterable externalCharges = Iterables.transform(sanitizedExternalChargesJson, new Function() { @Override public InvoiceItem apply(final InvoiceItemJson invoiceItemJson) { @@ -509,6 +496,40 @@ public InvoiceItemJson apply(final InvoiceItem input) { return Response.status(Status.OK).entity(createdExternalChargesJson).build(); } + private Iterable cloneRefundItemsWithValidCurrency(final Currency accountCurrency, final Iterable inputItems) throws InvoiceApiException { + try { + return Iterables.transform(inputItems, new Function() { + @Override + public InvoiceItemJson apply(final InvoiceItemJson input) { + if (input.getCurrency() != null) { + if (!input.getCurrency().equals(accountCurrency)) { + throw new IllegalArgumentException(input.getCurrency().toString()); + } + return input; + } else { + return new InvoiceItemJson(null, + input.getInvoiceId(), + null, input.getAccountId(), + input.getBundleId(), + input.getSubscriptionId(), + input.getPlanName(), + input.getPhaseName(), + input.getUsageName(), + input.getItemType(), + input.getDescription(), + input.getStartDate(), + input.getEndDate(), + input.getAmount(), + accountCurrency, + null); + } + } + }); + } catch (IllegalArgumentException e) { + throw new InvoiceApiException(ErrorCode.CURRENCY_INVALID, accountCurrency, e.getMessage()); + } + } + @Timed @GET @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS) diff --git a/pom.xml b/pom.xml index a731cc5cff..57731da908 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.41 + 0.42 killbill 0.15.4-SNAPSHOT diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java index c48147d59f..0c65c04015 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java @@ -20,6 +20,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -351,6 +352,42 @@ public void testExternalChargeOnNewInvoice() throws Exception { assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId()).size(), 3); } + + @Test(groups = "slow", description = "Can create multiple external charges") + public void testExternalCharges() throws Exception { + final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice(); + + // Get the invoices + assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId()).size(), 2); + + // Post an external charge + final BigDecimal chargeAmount = BigDecimal.TEN; + + final List externalCharges = new ArrayList(); + + // Does not pass currency to test on purpose that we will default to account currency + final InvoiceItem externalCharge1 = new InvoiceItem(); + externalCharge1.setAccountId(accountJson.getAccountId()); + externalCharge1.setAmount(chargeAmount); + externalCharge1.setDescription(UUID.randomUUID().toString()); + externalCharges.add(externalCharge1); + + final InvoiceItem externalCharge2 = new InvoiceItem(); + externalCharge2.setAccountId(accountJson.getAccountId()); + externalCharge2.setAmount(chargeAmount); + externalCharge2.setCurrency(Currency.valueOf(accountJson.getCurrency())); + externalCharge2.setDescription(UUID.randomUUID().toString()); + externalCharges.add(externalCharge2); + + final List createdExternalCharges = killBillClient.createExternalCharges(externalCharges, clock.getUTCNow(), false, createdBy, reason, comment); + assertEquals(createdExternalCharges.size(), 2); + assertEquals(createdExternalCharges.get(0).getCurrency().toString(), accountJson.getCurrency()); + assertEquals(createdExternalCharges.get(1).getCurrency().toString(), accountJson.getCurrency()); + + // Verify the total number of invoices + assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId()).size(), 3); + } + @Test(groups = "slow", description = "Can create an external charge and trigger a payment") public void testExternalChargeOnNewInvoiceWithAutomaticPayment() throws Exception { final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice(); From f9d03a75bdb318936c830e14dbdac96c2d8dca5b Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 11 Sep 2015 14:21:21 -0700 Subject: [PATCH 075/137] invoice, payment: See #393 Introduce a new success status in the invoice_payments table and make sure failed payment attempts against an invoice appear in that table. The goal is to ensure that when we return the payments attached to an invoice we see potential failed payments. --- .../invoice/api/InvoiceInternalApi.java | 2 +- .../integration/TestInvoicePayment.java | 62 +++++++++++++++++ .../api/svcs/DefaultInvoiceInternalApi.java | 4 +- .../calculator/InvoiceCalculatorUtils.java | 6 ++ .../invoice/dao/DefaultInvoiceDao.java | 6 +- .../invoice/dao/InvoicePaymentModelDao.java | 14 +++- .../invoice/dao/InvoicePaymentSqlDao.java | 13 ++++ .../invoice/model/DefaultInvoicePayment.java | 18 +++-- .../invoice/dao/InvoicePaymentSqlDao.sql.stg | 14 ++++ .../org/killbill/billing/invoice/ddl.sql | 1 + .../billing/invoice/dao/TestInvoiceDao.java | 54 ++++++++++++--- .../TestDefaultInvoiceGenerator.java | 2 +- .../invoice/proRations/InvoiceTestUtils.java | 1 + .../TestDefaultInvoiceFormatter.java | 4 +- .../sm/control/OperationControlCallback.java | 52 +++++++------- .../InvoicePaymentControlPluginApi.java | 22 +++++- .../billing/payment/api/TestPaymentApi.java | 69 ++++++++++++++++++- pom.xml | 2 +- 18 files changed, 291 insertions(+), 55 deletions(-) diff --git a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java index c54ef4716b..0403e60412 100644 --- a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java +++ b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java @@ -37,7 +37,7 @@ public interface InvoiceInternalApi { public BigDecimal getAccountBalance(UUID accountId, InternalTenantContext context); - public void notifyOfPayment(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, Currency processedCurrency, UUID paymentId, DateTime paymentDate, InternalCallContext context) throws InvoiceApiException; + public void notifyOfPayment(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, Currency processedCurrency, UUID paymentId, DateTime paymentDate, boolean success, InternalCallContext context) throws InvoiceApiException; public void notifyOfPayment(InvoicePayment invoicePayment, InternalCallContext context) throws InvoiceApiException; diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java index e68aab1374..13a3a43046 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java @@ -18,21 +18,29 @@ package org.killbill.billing.beatrix.integration; import java.math.BigDecimal; +import java.util.List; import org.joda.time.LocalDate; import org.killbill.billing.ObjectType; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountData; import org.killbill.billing.api.TestApiListener.NextEvent; +import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.catalog.api.ProductCategory; +import org.killbill.billing.entitlement.api.DefaultEntitlement; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceItem; +import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem; import org.killbill.billing.payment.api.Payment; +import org.killbill.billing.payment.api.PluginProperty; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; public class TestInvoicePayment extends TestIntegrationBase { @@ -97,4 +105,58 @@ public void testPartialPayments() throws Exception { assertTrue(accountBalance.compareTo(new BigDecimal("4.00")) == 0); } + + // + + @Test(groups = "slow") + public void testWithPaymentFailure() throws Exception { + + clock.setDay(new LocalDate(2012, 4, 1)); + + final AccountData accountData = getAccountData(1); + final Account account = createAccountWithNonOsgiPaymentMethod(accountData); + accountChecker.checkAccount(account.getId(), accountData, callContext); + + paymentPlugin.makeNextPaymentFailWithError(); + + createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE); + + busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR); + clock.addDays(30); + assertListenerStatus(); + + final List invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext); + assertEquals(invoices.size(), 2); + + final Invoice invoice1 = invoices.get(0).getInvoiceItems().get(0).getInvoiceItemType() == InvoiceItemType.RECURRING ? + invoices.get(0) : invoices.get(1); + assertTrue(invoice1.getBalance().compareTo(new BigDecimal("249.95")) == 0); + assertTrue(invoice1.getPaidAmount().compareTo(BigDecimal.ZERO) == 0); + assertTrue(invoice1.getChargedAmount().compareTo(new BigDecimal("249.95")) == 0); + assertEquals(invoice1.getPayments().size(), 1); + assertFalse(invoice1.getPayments().get(0).isSuccess()); + + BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(account.getId(), callContext); + assertTrue(accountBalance1.compareTo(new BigDecimal("249.95")) == 0); + + final List payments = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.of(), callContext); + assertEquals(payments.size(), 1); + + // Trigger the payment retry + busHandler.pushExpectedEvents(NextEvent.PAYMENT); + clock.addDays(8); + assertListenerStatus(); + + Invoice invoice2 = invoiceUserApi.getInvoice(invoice1.getId(), callContext); + assertTrue(invoice2.getBalance().compareTo(BigDecimal.ZERO) == 0); + assertTrue(invoice2.getPaidAmount().compareTo(new BigDecimal("249.95")) == 0); + assertTrue(invoice2.getChargedAmount().compareTo(new BigDecimal("249.95")) == 0); + assertEquals(invoice2.getPayments().size(), 1); + assertTrue(invoice2.getPayments().get(0).isSuccess()); + + BigDecimal accountBalance2 = invoiceUserApi.getAccountBalance(account.getId(), callContext); + assertTrue(accountBalance2.compareTo(BigDecimal.ZERO) == 0); + } + + } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java index db51bbb074..07f788bed0 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java @@ -94,8 +94,8 @@ public BigDecimal getAccountBalance(final UUID accountId, final InternalTenantCo } @Override - public void notifyOfPayment(final UUID invoiceId, final BigDecimal amount, final Currency currency, final Currency processedCurrency, final UUID paymentId, final DateTime paymentDate, final InternalCallContext context) throws InvoiceApiException { - final InvoicePayment invoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency); + public void notifyOfPayment(final UUID invoiceId, final BigDecimal amount, final Currency currency, final Currency processedCurrency, final UUID paymentId, final DateTime paymentDate, final boolean success, final InternalCallContext context) throws InvoiceApiException { + final InvoicePayment invoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, success); notifyOfPayment(invoicePayment, context); } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java index 1437745439..eceab78935 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java @@ -170,6 +170,9 @@ public static BigDecimal computeInvoiceAmountPaid(final Currency currency, @Null } for (final InvoicePayment invoicePayment : invoicePayments) { + if (!invoicePayment.isSuccess()) { + continue; + } if (InvoicePaymentType.ATTEMPT.equals(invoicePayment.getType())) { amountPaid = amountPaid.add(invoicePayment.getAmount()); } @@ -185,6 +188,9 @@ public static BigDecimal computeInvoiceAmountRefunded(final Currency currency, @ } for (final InvoicePayment invoicePayment : invoicePayments) { + if (!invoicePayment.isSuccess()) { + continue; + } if (InvoicePaymentType.REFUND.equals(invoicePayment.getType()) || InvoicePaymentType.CHARGED_BACK.equals(invoicePayment.getType())) { amountRefunded = amountRefunded.add(invoicePayment.getAmount()); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java index 655c5da6d8..0f4cdc059e 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java @@ -462,7 +462,7 @@ public boolean apply(final InvoicePaymentModelDao input) { final InvoicePaymentModelDao refund = new InvoicePaymentModelDao(UUIDs.randomUUID(), context.getCreatedDate(), InvoicePaymentType.REFUND, payment.getInvoiceId(), paymentId, context.getCreatedDate(), requestedPositiveAmount.negate(), - payment.getCurrency(), payment.getProcessedCurrency(), transactionExternalKey, payment.getId()); + payment.getCurrency(), payment.getProcessedCurrency(), transactionExternalKey, payment.getId(), true); transactional.create(refund, context); // Retrieve invoice after the Refund @@ -548,7 +548,7 @@ public boolean apply(final InvoicePaymentModelDao input) { final InvoicePaymentModelDao chargeBack = new InvoicePaymentModelDao(UUIDs.randomUUID(), context.getCreatedDate(), InvoicePaymentType.CHARGED_BACK, payment.getInvoiceId(), payment.getPaymentId(), context.getCreatedDate(), requestedChargedBackAmount.negate(), payment.getCurrency(), payment.getProcessedCurrency(), - null, payment.getId()); + null, payment.getId(), true); transactional.create(chargeBack, context); // Notify the bus since the balance of the invoice changed @@ -664,6 +664,8 @@ public boolean apply(final InvoicePaymentModelDao input) { }).orNull(); if (existingAttempt == null) { transactional.create(invoicePayment, context); + } else if (!existingAttempt.getSuccess() && invoicePayment.getSuccess()) { + transactional.updateAttempt(existingAttempt.getRecordId(), invoicePayment.getPaymentDate().toDate(), invoicePayment.getAmount(), invoicePayment.getCurrency(), invoicePayment.getProcessedCurrency(), context); } return null; } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java index 20a7c811ff..e05448f715 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java @@ -40,12 +40,13 @@ public class InvoicePaymentModelDao extends EntityModelDaoBase implements Entity private Currency processedCurrency; private String paymentCookieId; private UUID linkedInvoicePaymentId; + private Boolean success; public InvoicePaymentModelDao() { /* For the DAO mapper */ } public InvoicePaymentModelDao(final UUID id, final DateTime createdDate, final InvoicePaymentType type, final UUID invoiceId, final UUID paymentId, final DateTime paymentDate, final BigDecimal amount, final Currency currency, - final Currency processedCurrency, final String paymentCookieId, final UUID linkedInvoicePaymentId) { + final Currency processedCurrency, final String paymentCookieId, final UUID linkedInvoicePaymentId, final Boolean success) { super(id, createdDate, createdDate); this.type = type; this.invoiceId = invoiceId; @@ -56,12 +57,13 @@ public InvoicePaymentModelDao(final UUID id, final DateTime createdDate, final I this.processedCurrency = processedCurrency; this.paymentCookieId = paymentCookieId; this.linkedInvoicePaymentId = linkedInvoicePaymentId; + this.success = success; } public InvoicePaymentModelDao(final InvoicePayment invoicePayment) { this(invoicePayment.getId(), invoicePayment.getCreatedDate(), invoicePayment.getType(), invoicePayment.getInvoiceId(), invoicePayment.getPaymentId(), invoicePayment.getPaymentDate(), invoicePayment.getAmount(), invoicePayment.getCurrency(), invoicePayment.getProcessedCurrency(), invoicePayment.getPaymentCookieId(), - invoicePayment.getLinkedInvoicePaymentId()); + invoicePayment.getLinkedInvoicePaymentId(), invoicePayment.isSuccess()); } public InvoicePaymentType getType() { @@ -136,6 +138,14 @@ public void setLinkedInvoicePaymentId(final UUID linkedInvoicePaymentId) { this.linkedInvoicePaymentId = linkedInvoicePaymentId; } + public Boolean getSuccess() { + return success; + } + + public void setSuccess(final Boolean success) { + this.success = success; + } + @Override public String toString() { final StringBuilder sb = new StringBuilder(); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java index 626dda4de5..279aea38b1 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java @@ -19,16 +19,19 @@ package org.killbill.billing.invoice.dao; import java.math.BigDecimal; +import java.util.Date; import java.util.List; import java.util.UUID; import org.killbill.billing.callcontext.InternalTenantContext; +import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.invoice.api.InvoicePayment; import org.killbill.billing.util.entity.dao.EntitySqlDao; import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate; import org.skife.jdbi.v2.sqlobject.Bind; import org.skife.jdbi.v2.sqlobject.BindBean; import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; @EntitySqlDaoStringTemplate public interface InvoicePaymentSqlDao extends EntitySqlDao { @@ -64,4 +67,14 @@ List getChargeBacksByAccountId(@Bind("accountId") final @SqlQuery List getChargebacksByPaymentId(@Bind("paymentId") final String paymentId, @BindBean final InternalTenantContext context); + + + + @SqlUpdate + void updateAttempt(@Bind("recordId") Long recordId, + @Bind("paymentDate") final Date paymentDate, + @Bind("amount") final BigDecimal amount, + @Bind("currency") final Currency currency, + @Bind("processedCurrency") final Currency processedCurrency, + @BindBean final InternalTenantContext context); } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java index abb867357a..54363abb5a 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java @@ -41,21 +41,22 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment private final Currency processedCurrency; private final String paymentCookieId; private final UUID linkedInvoicePaymentId; + private final Boolean isSuccess; public DefaultInvoicePayment(final InvoicePaymentType type, final UUID paymentId, final UUID invoiceId, final DateTime paymentDate, - final BigDecimal amount, final Currency currency, final Currency processedCurrency) { - this(UUIDs.randomUUID(), null, type, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, null, null); + final BigDecimal amount, final Currency currency, final Currency processedCurrency, final Boolean isSuccess) { + this(UUIDs.randomUUID(), null, type, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, null, null, isSuccess); } public DefaultInvoicePayment(final UUID id, final InvoicePaymentType type, final UUID paymentId, final UUID invoiceId, final DateTime paymentDate, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final Currency processedCurrency, @Nullable final String paymentCookieId, @Nullable final UUID linkedInvoicePaymentId) { - this(id, null, type, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, paymentCookieId, linkedInvoicePaymentId); + this(id, null, type, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, paymentCookieId, linkedInvoicePaymentId, true); } public DefaultInvoicePayment(final UUID id, @Nullable final DateTime createdDate, final InvoicePaymentType type, final UUID paymentId, final UUID invoiceId, final DateTime paymentDate, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final Currency processedCurrency, @Nullable final String paymentCookieId, - @Nullable final UUID linkedInvoicePaymentId) { + @Nullable final UUID linkedInvoicePaymentId, final Boolean isSuccess) { super(id, createdDate, createdDate); this.type = type; this.paymentId = paymentId; @@ -66,6 +67,7 @@ public DefaultInvoicePayment(final UUID id, @Nullable final DateTime createdDate this.processedCurrency =processedCurrency; this.paymentCookieId = paymentCookieId; this.linkedInvoicePaymentId = linkedInvoicePaymentId; + this.isSuccess = isSuccess; } public DefaultInvoicePayment(final InvoicePaymentModelDao invoicePaymentModelDao) { @@ -73,7 +75,8 @@ public DefaultInvoicePayment(final InvoicePaymentModelDao invoicePaymentModelDao invoicePaymentModelDao.getPaymentId(), invoicePaymentModelDao.getInvoiceId(), invoicePaymentModelDao.getPaymentDate(), invoicePaymentModelDao.getAmount(), invoicePaymentModelDao.getCurrency(), invoicePaymentModelDao.getProcessedCurrency(), invoicePaymentModelDao.getPaymentCookieId(), - invoicePaymentModelDao.getLinkedInvoicePaymentId()); + invoicePaymentModelDao.getLinkedInvoicePaymentId(), + invoicePaymentModelDao.getSuccess()); } @Override @@ -121,4 +124,9 @@ public Currency getProcessedCurrency() { return processedCurrency; } + @Override + public Boolean isSuccess() { + return isSuccess; + } + } diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg index fe0410fac9..b9e4dab87b 100644 --- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg +++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg @@ -12,6 +12,7 @@ tableFields(prefix) ::= << , processed_currency , payment_cookie_id , linked_invoice_payment_id +, success , created_by , created_date >> @@ -26,6 +27,7 @@ tableValues() ::= << , :processedCurrency , :paymentCookieId , :linkedInvoicePaymentId +, :success , :createdBy , :createdDate >> @@ -70,6 +72,7 @@ getRemainingAmountPaid() ::= << SELECT SUM(amount) FROM WHERE (id = :invoicePaymentId OR linked_invoice_payment_id = :invoicePaymentId) + AND success ; >> @@ -103,3 +106,14 @@ getChargebacksByPaymentId() ::= << ; >> + +updateAttempt() ::= << + UPDATE + SET success = true, + payment_date = :paymentDate, + amount = :amount, + processed_currency = :processedCurrency + WHERE record_id = :recordId + + ; +>> diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql index f8b1375d46..bb1ed4ed81 100644 --- a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql +++ b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql @@ -63,6 +63,7 @@ CREATE TABLE invoice_payments ( processed_currency varchar(3) NOT NULL, payment_cookie_id varchar(255) DEFAULT NULL, linked_invoice_payment_id varchar(36) DEFAULT NULL, + success bool DEFAULT true, created_by varchar(50) NOT NULL, created_date datetime NOT NULL, account_record_id bigint /*! unsigned */ not null, diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java index 0823824f80..8667bb3cb2 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java @@ -150,7 +150,7 @@ public void testInvoicePayment() throws InvoiceApiException { final BigDecimal paymentAmount = new BigDecimal("11.00"); final UUID paymentId = UUID.randomUUID(); - final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD, Currency.USD); + final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD, Currency.USD, true); invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context); final InvoiceModelDao retrievedInvoice = invoiceDao.getById(invoiceId, context); @@ -521,7 +521,7 @@ public void testAccountBalance() throws EntityPersistenceException { invoiceUtil.createInvoiceItem(item2, context); final BigDecimal payment1 = new BigDecimal("48.0"); - final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD); + final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true); invoiceUtil.createPayment(payment, context); final BigDecimal balance = invoiceDao.getAccountBalance(accountId, context); @@ -586,7 +586,7 @@ public void testAccountBalanceWithNoInvoiceItems() throws EntityPersistenceExcep invoiceUtil.createInvoice(invoice1, true, context); final BigDecimal payment1 = new BigDecimal("48.0"); - final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD); + final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true); invoiceUtil.createPayment(payment, context); final BigDecimal balance = invoiceDao.getAccountBalance(accountId, context); @@ -627,7 +627,7 @@ private void testAccountBalanceWithRefundInternal(final boolean withAdjustment) // Pay the whole thing final UUID paymentId = UUID.randomUUID(); final BigDecimal payment1 = rate1; - final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD); + final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true); invoiceUtil.createPayment(payment, context); balance = invoiceDao.getAccountBalance(accountId, context); assertEquals(balance.compareTo(new BigDecimal("0.00")), 0); @@ -676,7 +676,7 @@ private void testRefundWithRepairAndInvoiceItemAdjustmentInternal(final BigDecim // Pay the whole thing final UUID paymentId = UUID.randomUUID(); final BigDecimal payment1 = amount; - final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), new DateTime(), payment1, Currency.USD, Currency.USD); + final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true); invoiceUtil.createPayment(payment, context); balancePriorRefund = invoiceDao.getAccountBalance(accountId, context); assertEquals(balancePriorRefund.compareTo(new BigDecimal("0.00")), 0); @@ -781,7 +781,7 @@ private void testAccountBalanceWithRefundAndCBAInternal(final boolean withAdjust // Pay the whole thing final UUID paymentId = UUID.randomUUID(); final BigDecimal payment1 = amount1.add(rate1); - final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD); + final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true); invoiceUtil.createPayment(payment, context); balance = invoiceDao.getAccountBalance(accountId, context); assertEquals(balance.compareTo(new BigDecimal("0.00")), 0); @@ -872,7 +872,7 @@ public void testAccountBalanceWithAllSortsOfThings() throws EntityPersistenceExc // Pay the whole thing final BigDecimal payment1 = amount1.add(rate1); - final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD); + final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true); invoiceUtil.createPayment(payment, context); balance = invoiceDao.getAccountBalance(accountId, context); assertEquals(balance.compareTo(new BigDecimal("0.00")), 0); @@ -1322,7 +1322,7 @@ public void testRefundedInvoiceWithInvoiceItemAdjustmentWithRepair() throws Invo // SECOND CREATE THE PAYMENT final BigDecimal paymentAmount = new BigDecimal("239.00"); final UUID paymentId = UUID.randomUUID(); - final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, clock.getUTCNow(), paymentAmount, Currency.USD, Currency.USD); + final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, clock.getUTCNow(), paymentAmount, Currency.USD, Currency.USD, true); invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context); // AND THEN THIRD THE REFUND @@ -1467,7 +1467,7 @@ public void testRefundWithCBAPartiallyConsumed() throws Exception { final UUID paymentId = UUID.randomUUID(); final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), clock.getUTCNow().plusDays(12), new BigDecimal("10.0"), - Currency.USD, Currency.USD); + Currency.USD, Currency.USD, true); invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context); @@ -1533,7 +1533,7 @@ public void testRefundCBAFullyConsumedTwice() throws Exception { final UUID paymentId = UUID.randomUUID(); final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), clock.getUTCNow().plusDays(12), paymentAmount, - Currency.USD, Currency.USD); + Currency.USD, Currency.USD, true); invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context); // Create invoice 2 @@ -1616,6 +1616,40 @@ public void testCantDeleteCBAIfInvoiceBalanceBecomesNegative() throws Exception invoiceUtil.verifyInvoice(invoice1.getId(), 0.00, 10.00, context); } + + @Test(groups = "slow") + public void testWithFailedPaymentAttempt() throws Exception { + final UUID accountId = account.getId(); + final Invoice invoice = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD); + invoiceUtil.createInvoice(invoice, true, context); + + final UUID bundleId = UUID.randomUUID(); + final UUID subscriptionId = UUID.randomUUID(); + final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice.getId(), accountId, bundleId, subscriptionId, "test plan", "test ZOO", clock.getUTCNow().plusMonths(-1).toLocalDate(), clock.getUTCNow().toLocalDate(), + BigDecimal.TEN, BigDecimal.TEN, Currency.USD); + invoiceUtil.createInvoiceItem(item1, context); + + final InvoiceModelDao retrievedInvoice = invoiceDao.getById(invoice.getId(), context); + assertEquals(retrievedInvoice.getInvoicePayments().size(), 0); + + + final UUID paymentId = UUID.randomUUID(); + final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), clock.getUTCNow().plusDays(12), BigDecimal.TEN, Currency.USD, Currency.USD, false); + invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context); + + final InvoiceModelDao retrievedInvoice1 = invoiceDao.getById(invoice.getId(), context); + assertEquals(retrievedInvoice1.getInvoicePayments().size(), 1); + assertEquals(retrievedInvoice1.getInvoicePayments().get(0).getSuccess(), Boolean.FALSE); + + final DefaultInvoicePayment defaultInvoicePayment2 = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), clock.getUTCNow().plusDays(12), BigDecimal.TEN, Currency.USD, Currency.USD, true); + invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment2), context); + + final InvoiceModelDao retrievedInvoice2 = invoiceDao.getById(invoice.getId(), context); + assertEquals(retrievedInvoice2.getInvoicePayments().size(), 1); + assertEquals(retrievedInvoice2.getInvoicePayments().get(0).getSuccess(), Boolean.TRUE); + } + + private void createCredit(final UUID accountId, final LocalDate effectiveDate, final BigDecimal creditAmount) { createCredit(accountId, null, effectiveDate, creditAmount); } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java index 4882cc2155..7cd34eeedb 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java @@ -918,7 +918,7 @@ public void testRepairForPaidInvoice() throws CatalogApiException, InvoiceApiExc // pay the invoice invoice1.addPayment(new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), april25.toDateTimeAtCurrentTime(), TEN, - Currency.USD, Currency.USD)); + Currency.USD, Currency.USD, true)); assertEquals(invoice1.getBalance().compareTo(ZERO), 0); // change the plan (i.e. repair) on start date diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java index 1833d6b636..bd3103c715 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java @@ -134,6 +134,7 @@ public static InvoicePayment createAndPersistPayment(final InvoiceInternalApi in Mockito.when(payment.getAmount()).thenReturn(amount); Mockito.when(payment.getCurrency()).thenReturn(currency); Mockito.when(payment.getProcessedCurrency()).thenReturn(currency); + Mockito.when(payment.isSuccess()).thenReturn(true); invoicePaymentApi.notifyOfPayment(payment, callContext); diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java index 5e18596361..df0a9d7a08 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java @@ -134,9 +134,9 @@ public void testMergeItems() throws Exception { invoice.addInvoiceItem(creditBalanceAdjInvoiceItem2); invoice.addInvoiceItem(refundAdjInvoiceItem); invoice.addPayment(new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice.getId(), clock.getUTCNow(), BigDecimal.TEN, - Currency.USD, Currency.USD)); + Currency.USD, Currency.USD, true)); invoice.addPayment(new DefaultInvoicePayment(InvoicePaymentType.REFUND, UUID.randomUUID(), invoice.getId(), clock.getUTCNow(), BigDecimal.ONE.negate(), - Currency.USD, Currency.USD)); + Currency.USD, Currency.USD, true)); // Check the scenario Assert.assertEquals(invoice.getBalance().doubleValue(), 0.00); Assert.assertEquals(invoice.getCreditedAmount().doubleValue(), 11.00); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java index add2adf747..025bef2491 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java @@ -17,6 +17,7 @@ package org.killbill.billing.payment.core.sm.control; +import java.math.BigDecimal; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -117,35 +118,34 @@ public PluginDispatcherReturnType doOperation() throws Operatio final PaymentTransaction transaction = ((PaymentStateControlContext) paymentStateContext).getCurrentTransaction(); success = transaction.getTransactionStatus() == TransactionStatus.SUCCESS || transaction.getTransactionStatus() == TransactionStatus.PENDING; + final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(), + paymentStateContext.getPaymentMethodId(), + paymentStateControlContext.getAttemptId(), + result.getId(), + result.getExternalKey(), + transaction.getId(), + paymentStateContext.getPaymentTransactionExternalKey(), + PaymentApiType.PAYMENT_TRANSACTION, + paymentStateContext.getTransactionType(), + null, + transaction.getAmount(), + transaction.getCurrency(), + transaction.getProcessedAmount(), + transaction.getProcessedCurrency(), + paymentStateControlContext.isApiPayment(), + paymentStateContext.getCallContext()); if (success) { - final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(), - paymentStateContext.getPaymentMethodId(), - paymentStateControlContext.getAttemptId(), - result.getId(), - result.getExternalKey(), - transaction.getId(), - paymentStateContext.getPaymentTransactionExternalKey(), - PaymentApiType.PAYMENT_TRANSACTION, - paymentStateContext.getTransactionType(), - null, - transaction.getAmount(), - transaction.getCurrency(), - transaction.getProcessedAmount(), - transaction.getProcessedCurrency(), - paymentStateControlContext.isApiPayment(), - paymentStateContext.getCallContext()); - executePluginOnSuccessCalls(paymentStateControlContext.getPaymentControlPluginNames(), updatedPaymentControlContext); return PluginDispatcher.createPluginDispatcherReturnType(OperationResult.SUCCESS); } else { - throw new OperationException(null, executePluginOnFailureCallsAndSetRetryDate(paymentStateControlContext, paymentControlContext)); + throw new OperationException(null, executePluginOnFailureCallsAndSetRetryDate(updatedPaymentControlContext)); } } catch (final PaymentApiException e) { // Wrap PaymentApiException, and throw a new OperationException with an ABORTED/FAILURE state based on the retry result. - throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(paymentStateControlContext, paymentControlContext)); + throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(paymentControlContext)); } catch (final RuntimeException e) { // Attempts to set the retry date in context if needed. - executePluginOnFailureCallsAndSetRetryDate(paymentStateControlContext, paymentControlContext); + executePluginOnFailureCallsAndSetRetryDate(paymentControlContext); throw e; } } @@ -202,7 +202,7 @@ private PriorPaymentControlResult executePluginPriorCalls(final List pay } protected void executePluginOnSuccessCalls(final List paymentControlPluginNames, final PaymentControlContext paymentControlContext) { - // Values that were obtained/chnaged after the payment call was made (paymentId, processedAmount, processedCurrency,... needs to be extracted from the paymentControlContext) + // Values that were obtained/changed after the payment call was made (paymentId, processedAmount, processedCurrency,... needs to be extracted from the paymentControlContext) // paymentId, paymentExternalKey, transactionAmount, transaction currency are extracted from paymentControlContext which was update from the operation result. final OnSuccessPaymentControlResult result = controlPluginRunner.executePluginOnSuccessCalls(paymentStateContext.getAccount(), paymentStateContext.getPaymentMethodId(), @@ -225,7 +225,7 @@ protected void executePluginOnSuccessCalls(final List paymentControlPlug adjustStateContextPluginProperties(paymentStateContext, result.getAdjustedPluginProperties()); } - private OperationResult executePluginOnFailureCallsAndSetRetryDate(final PaymentStateControlContext paymentStateControlContext, final PaymentControlContext paymentControlContext) { + private OperationResult executePluginOnFailureCallsAndSetRetryDate(final PaymentControlContext paymentControlContext) { final DateTime retryDate = executePluginOnFailureCalls(paymentStateControlContext.getPaymentControlPluginNames(), paymentControlContext); if (retryDate != null) { ((PaymentStateControlContext) paymentStateContext).setRetryDate(retryDate); @@ -238,11 +238,11 @@ private DateTime executePluginOnFailureCalls(final List paymentControlPl final OnFailurePaymentControlResult result = controlPluginRunner.executePluginOnFailureCalls(paymentStateContext.getAccount(), paymentControlContext.getPaymentMethodId(), paymentStateControlContext.getAttemptId(), - paymentStateContext.getPaymentId(), - paymentStateContext.getPaymentExternalKey(), - paymentStateContext.getPaymentTransactionExternalKey(), + paymentControlContext.getPaymentId(), + paymentControlContext.getPaymentExternalKey(), + paymentControlContext.getTransactionExternalKey(), PaymentApiType.PAYMENT_TRANSACTION, - paymentStateContext.getTransactionType(), + paymentControlContext.getTransactionType(), null, paymentControlContext.getAmount(), paymentControlContext.getCurrency(), diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java index 851030134f..1ea86bd885 100644 --- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java @@ -149,7 +149,7 @@ public OnSuccessPaymentControlResult onSuccessCall(final PaymentControlContext p case PURCHASE: final UUID invoiceId = getInvoiceId(pluginProperties); existingInvoicePayment = invoiceApi.getInvoicePaymentForAttempt(paymentControlContext.getPaymentId(), internalContext); - if (existingInvoicePayment != null) { + if (existingInvoicePayment != null && existingInvoicePayment.isSuccess()) { log.info("onSuccessCall was already completed for payment purchase :" + paymentControlContext.getPaymentId()); } else { invoiceApi.notifyOfPayment(invoiceId, @@ -158,6 +158,7 @@ public OnSuccessPaymentControlResult onSuccessCall(final PaymentControlContext p paymentControlContext.getProcessedCurrency(), paymentControlContext.getPaymentId(), paymentControlContext.getCreatedDate(), + true, internalContext); } break; @@ -193,11 +194,28 @@ public OnSuccessPaymentControlResult onSuccessCall(final PaymentControlContext p } @Override - public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable properties) throws PaymentControlApiException { + public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable pluginProperties) throws PaymentControlApiException { final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext); final TransactionType transactionType = paymentControlContext.getTransactionType(); switch (transactionType) { case PURCHASE: + final UUID invoiceId = getInvoiceId(pluginProperties); + if (paymentControlContext.getPaymentId() != null) { + try { + invoiceApi.notifyOfPayment(invoiceId, + paymentControlContext.getAmount(), + paymentControlContext.getCurrency(), + // processed currency may be null so we use currency; processed currency will be updated if/when payment succeeds + paymentControlContext.getCurrency(), + paymentControlContext.getPaymentId(), + paymentControlContext.getCreatedDate(), + false, + internalContext); + } catch (InvoiceApiException e) { + log.error("InvoicePaymentControlPluginApi onFailureCall failed ton update invoice for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType = " + transactionType, e); + } + } + final DateTime nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext); return new DefaultFailureCallResult(nextRetryDate); case REFUND: diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java index a5b2a5a8f4..0960badf90 100644 --- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java +++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java @@ -43,6 +43,7 @@ import org.killbill.billing.payment.provider.MockPaymentProviderPlugin; import org.killbill.bus.api.PersistentBus.EventBusException; import org.testng.Assert; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -54,6 +55,8 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB { + private MockPaymentProviderPlugin mockPaymentProviderPlugin; + final PaymentOptions INVOICE_PAYMENT = new PaymentOptions() { @Override public boolean isExternalPayment() { @@ -68,9 +71,16 @@ public List getPaymentControlPluginNames() { private Account account; + @BeforeClass(groups = "slow") + protected void beforeClass() throws Exception { + super.beforeClass(); + mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME); + } + @BeforeMethod(groups = "slow") public void beforeMethod() throws Exception { super.beforeMethod(); + mockPaymentProviderPlugin.clear(); account = testHelper.createTestAccount("bobo@gmail.com", true); } @@ -241,7 +251,6 @@ public void testCreateSuccessPurchaseWithPaymentControl() throws PaymentApiExcep requestedAmount, new BigDecimal("1.0"), Currency.USD)); - final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey, createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext); @@ -272,6 +281,64 @@ public void testCreateSuccessPurchaseWithPaymentControl() throws PaymentApiExcep assertEquals(attempts.size(), 1); } + + + @Test(groups = "slow") + public void testCreateFailedPurchaseWithPaymentControl() throws PaymentApiException, InvoiceApiException, EventBusException { + + final BigDecimal requestedAmount = BigDecimal.TEN; + final UUID subscriptionId = UUID.randomUUID(); + final UUID bundleId = UUID.randomUUID(); + final LocalDate now = clock.getUTCToday(); + + final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD); + + final String paymentExternalKey = invoice.getId().toString(); + final String transactionExternalKey = "brrrrrr"; + + mockPaymentProviderPlugin.makeNextPaymentFailWithError(); + + invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(), + subscriptionId, + bundleId, + "test plan", "test phase", null, + now, + now.plusMonths(1), + requestedAmount, + new BigDecimal("1.0"), + Currency.USD)); + try { + paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey, + createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext); + } catch (final PaymentApiException expected) { + } + + + final List accountPayments = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.of(), callContext); + assertEquals(accountPayments.size(), 1); + final Payment payment = accountPayments.get(0); + assertEquals(payment.getExternalKey(), paymentExternalKey); + assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId()); + assertEquals(payment.getAccountId(), account.getId()); + assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0); + assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0); + assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0); + assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0); + assertEquals(payment.getCurrency(), Currency.USD); + + assertEquals(payment.getTransactions().size(), 1); + assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey); + assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId()); + assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.USD); + assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); // This is weird... + assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.USD); + + assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE); + assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE); + } + + @Test(groups = "slow") public void testCreateAbortedPurchaseWithPaymentControl() throws InvoiceApiException, EventBusException { diff --git a/pom.xml b/pom.xml index 57731da908..4aa4e0c02a 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.42 + 0.43 killbill 0.15.4-SNAPSHOT From fc2c3c737f0ddb95ffa460a04174f6c460ea2b9a Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sat, 12 Sep 2015 18:05:21 -0700 Subject: [PATCH 076/137] invoice: Fix usage issue (missing usage items or incomplete). The fix is ported from 0.14.1 branch and is related to an issue with the ordering if the billing events which are first ordered by subscription and then effectiveDate (and not first with effectiveDate). Added a test case which reproduced the issue: https://github.com/killbill/killbill-integration-tests/blob/master/killbill-integration-tests/core/usage/test_default_catalog_consumable_in_arrear.rb#L381 --- .../generator/UsageInvoiceItemGenerator.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java index 7750be8ba6..8654de5a3a 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java @@ -26,6 +26,8 @@ import javax.annotation.Nullable; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalCallContext; @@ -80,6 +82,9 @@ public List generateItems(final Account account, final Map> perSubscriptionConsumableInArrearUsageItems = extractPerSubscriptionExistingConsumableInArrearUsageItems(eventSet.getUsages(), existingInvoices); try { + + final LocalDate minBillingEventDate = getMinBillingEventDate(eventSet, account.getTimeZone()); + final List items = Lists.newArrayList(); final Iterator events = eventSet.iterator(); @@ -103,7 +108,7 @@ public boolean apply(@Nullable final Usage input) { input.getBillingMode() == BillingMode.IN_ARREAR); } })) { - rawUsageOptimizerResult = rawUsageOptimizer.getConsumableInArrearUsage(new LocalDate(event.getEffectiveDate(), account.getTimeZone()), targetDate, Iterables.concat(perSubscriptionConsumableInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext); + rawUsageOptimizerResult = rawUsageOptimizer.getConsumableInArrearUsage(minBillingEventDate, targetDate, Iterables.concat(perSubscriptionConsumableInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext); } // None of the billing events report any usage (CONSUMABLE/IN_ARREAR) sections @@ -141,6 +146,19 @@ public boolean apply(@Nullable final Usage input) { } } + + private LocalDate getMinBillingEventDate(final BillingEventSet eventSet, final DateTimeZone accountTimeZone) { + DateTime minDate = null; + final Iterator events = eventSet.iterator(); + while (events.hasNext()) { + final BillingEvent cur = events.next(); + if (minDate == null || minDate.compareTo(cur.getEffectiveDate()) > 0) { + minDate = cur.getEffectiveDate(); + } + } + return new LocalDate(minDate, accountTimeZone); + } + private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final Map nextBillingCycleDates, final BillingMode usageBillingMode, final Map perSubscriptionFutureNotificationDates) { if (usageBillingMode == BillingMode.IN_ADVANCE) { throw new IllegalStateException("Not implemented Yet)"); From 2e8126e7fe21f6bf18b5c713e0e9d57d1618bcd4 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sat, 12 Sep 2015 18:07:44 -0700 Subject: [PATCH 077/137] invoice: cleanup unsued file --- .../generator/FixedAndRecurringGenerator.java | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringGenerator.java diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringGenerator.java deleted file mode 100644 index 8fe996e31e..0000000000 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringGenerator.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2014-2015 Groupon, Inc - * Copyright 2014-2015 The Billing Project, LLC - * - * The Billing Project licenses this file to you 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 org.killbill.billing.invoice.generator; - -public class FixedAndRecurringGenerator { -} From 862d141e0524ff32c0666de0d36437e87053117b Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sat, 12 Sep 2015 18:15:48 -0700 Subject: [PATCH 078/137] invoice: @See #361. Previous fix b1790a85d8f3e64677f309ca03758a43eb597a29 was incorrect. The code will now filter the $0 items a bit later to allow using the date info they contain and which is required to correct computation of next billing date (IN_ADVANCE billing mode) --- .../FixedAndRecurringInvoiceItemGenerator.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java index 8f5f03fa89..be6c3d7980 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java @@ -165,7 +165,6 @@ private List processRecurringEvent(final UUID invoiceId, final UUID if (rate != null) { final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency); - if (BigDecimal.ZERO.compareTo(amount) != 0) { final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(), @@ -175,11 +174,11 @@ private List processRecurringEvent(final UUID invoiceId, final UUID itemDatum.getStartDate(), itemDatum.getEndDate(), amount, rate, currency); items.add(recurringItem); - - } } } updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate); + // Filtering $0 items needs to occur after we compute nextNotificationDate, because these items contain important date info for the case of IN_ADVANCE billing + remove0$RecurringItems(items); } } @@ -193,6 +192,16 @@ private List processRecurringEvent(final UUID invoiceId, final UUID return items; } + private void remove0$RecurringItems(final List items) { + final Iterator it = items.iterator(); + while (it.hasNext()) { + final InvoiceItem item = it.next(); + if (item.getAmount().compareTo(BigDecimal.ZERO) == 0) { + it.remove(); + } + } + } + private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List newProposedItems, final BillingMode billingMode, final Map perSubscriptionFutureNotificationDates) { LocalDate nextNotificationDate = null; From 7d43124acb3a1f7cecfea487f42bd95cab3945a9 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sat, 12 Sep 2015 18:54:10 -0700 Subject: [PATCH 079/137] catalog: Add ANNUAL plan for product Sport (catalog SpyCarAdvanced) --- catalog/src/test/resources/SpyCarAdvanced.xml | 46 +++++++++++++++++++ .../src/main/resources/SpyCarAdvanced.xml | 46 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/catalog/src/test/resources/SpyCarAdvanced.xml b/catalog/src/test/resources/SpyCarAdvanced.xml index da2b95bc97..f02a9cdce4 100644 --- a/catalog/src/test/resources/SpyCarAdvanced.xml +++ b/catalog/src/test/resources/SpyCarAdvanced.xml @@ -292,6 +292,51 @@ + + Sports + + + + DAYS + 30 + + + + + + + + + + UNLIMITED + + + ANNUAL + + + GBP + 3750.00 + + + EUR + 4250.00 + + + USD + 5000.00 + + + JPY + 500.00 + + + BTC + 5.0 + + + + + Super @@ -749,6 +794,7 @@ standard-annual standard-monthly + sports-annual sports-monthly super-monthly remotecontrol-monthly diff --git a/profiles/killbill/src/main/resources/SpyCarAdvanced.xml b/profiles/killbill/src/main/resources/SpyCarAdvanced.xml index da2b95bc97..f02a9cdce4 100644 --- a/profiles/killbill/src/main/resources/SpyCarAdvanced.xml +++ b/profiles/killbill/src/main/resources/SpyCarAdvanced.xml @@ -292,6 +292,51 @@ + + Sports + + + + DAYS + 30 + + + + + + + + + + UNLIMITED + + + ANNUAL + + + GBP + 3750.00 + + + EUR + 4250.00 + + + USD + 5000.00 + + + JPY + 500.00 + + + BTC + 5.0 + + + + + Super @@ -749,6 +794,7 @@ standard-annual standard-monthly + sports-annual sports-monthly super-monthly remotecontrol-monthly From 86ae0d241deaa66d96e243d65b90af838bbc331d Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sun, 13 Sep 2015 11:07:11 -0700 Subject: [PATCH 080/137] invoice: @See #361. Previous fix 862d141e0524ff32c0666de0d36437e87053117b (and initial fix b1790a85d8f3e64677f309ca03758a43eb597a29) was incorrect Move the removal of the $0 recurring and usage items as late as possible but inside InvoiceWithMetadata to potentially set resulting invoice to null if nothing remains --- .../generator/DefaultInvoiceGenerator.java | 5 ++++ ...FixedAndRecurringInvoiceItemGenerator.java | 11 -------- .../generator/InvoiceItemGenerator.java | 1 - .../generator/InvoiceWithMetadata.java | 28 +++++++++++++++++-- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index 6447349272..64f07ba20a 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -18,7 +18,9 @@ package org.killbill.billing.invoice.generator; +import java.math.BigDecimal; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; @@ -34,6 +36,7 @@ import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItem; +import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; import org.killbill.billing.invoice.model.DefaultInvoice; import org.killbill.billing.junction.BillingEventSet; @@ -84,6 +87,7 @@ public InvoiceWithMetadata generateInvoice(final Account account, @Nullable fina final List usageItems = usageInvoiceItemGenerator.generateItems(account, invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context); invoice.addInvoiceItems(usageItems); + return new InvoiceWithMetadata(invoice.getInvoiceItems().isEmpty() ? null : invoice, perSubscriptionFutureNotificationDates); } @@ -109,4 +113,5 @@ private LocalDate adjustTargetDate(final List existingInvoices, final L } return maxDate; } + } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java index be6c3d7980..390afcabd9 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java @@ -177,8 +177,6 @@ private List processRecurringEvent(final UUID invoiceId, final UUID } } updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate); - // Filtering $0 items needs to occur after we compute nextNotificationDate, because these items contain important date info for the case of IN_ADVANCE billing - remove0$RecurringItems(items); } } @@ -192,15 +190,6 @@ private List processRecurringEvent(final UUID invoiceId, final UUID return items; } - private void remove0$RecurringItems(final List items) { - final Iterator it = items.iterator(); - while (it.hasNext()) { - final InvoiceItem item = it.next(); - if (item.getAmount().compareTo(BigDecimal.ZERO) == 0) { - it.remove(); - } - } - } private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List newProposedItems, final BillingMode billingMode, final Map perSubscriptionFutureNotificationDates) { diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java index 6009549ed9..22b0bac63c 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java @@ -40,5 +40,4 @@ public abstract List generateItems(final Account account, final UUI final Currency targetCurrency, Map perSubscriptionFutureNotificationDate, final InternalCallContext context) throws InvoiceApiException; - } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java index 50f41da1b1..3673a4c283 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java @@ -17,7 +17,10 @@ package org.killbill.billing.invoice.generator; +import java.math.BigDecimal; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -34,13 +37,15 @@ public class InvoiceWithMetadata { - private final Invoice invoice; private final Map perSubscriptionFutureNotificationDates; - public InvoiceWithMetadata(final Invoice invoice, final Map perSubscriptionFutureNotificationDates) { - this.invoice = invoice; + private Invoice invoice; + + public InvoiceWithMetadata(final Invoice originalInvoice, final Map perSubscriptionFutureNotificationDates) { + this.invoice = originalInvoice; this.perSubscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates; build(); + remove$0RecurringAndUsageItems(); } public Invoice getInvoice() { @@ -74,6 +79,23 @@ public boolean apply(final InvoiceItem input) { }); } + protected void remove$0RecurringAndUsageItems() { + if (invoice != null) { + final Iterator it = invoice.getInvoiceItems().iterator(); + while (it.hasNext()) { + final InvoiceItem item = it.next(); + if ((item.getInvoiceItemType() == InvoiceItemType.RECURRING || item.getInvoiceItemType() == InvoiceItemType.USAGE) && + item.getAmount().compareTo(BigDecimal.ZERO) == 0) { + it.remove(); + } + } + if (invoice.getInvoiceItems().isEmpty()) { + invoice = null; + } + } + } + + public static class SubscriptionFutureNotificationDates { private final BillingMode recurringBillingMode; From c8462b05fa51d9d19f3e1df2c1347ad5bbfcdd99 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sun, 13 Sep 2015 12:54:03 -0700 Subject: [PATCH 081/137] jaxrs: Code review integration for 159ea86613628789452ce4c703b2f4bc0758ce11 Reorder InvoicePayment (since original ordering from DB level was lost after we eliminated Payment duplicates) --- .../jaxrs/resources/InvoiceResource.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java index a7dbf1f875..180a800486 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java @@ -24,6 +24,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -114,6 +115,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; import com.google.inject.Inject; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; @@ -137,6 +139,14 @@ public class InvoiceResource extends JaxRsResourceBase { private final TenantUserApi tenantApi; private final Locale defaultLocale; + private static final Ordering INVOICE_PAYMENT_ORDERING = Ordering.from(new Comparator() { + @Override + public int compare(final InvoicePaymentJson o1, final InvoicePaymentJson o2) { + return o1.getTransactions().get(0).getEffectiveDate().compareTo(o2.getTransactions().get(0).getEffectiveDate()); + } + }); + + @Inject public InvoiceResource(final AccountUserApi accountUserApi, final InvoiceUserApi invoiceApi, @@ -530,6 +540,7 @@ public InvoiceItemJson apply(final InvoiceItemJson input) { } } + @Timed @GET @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS) @@ -541,8 +552,8 @@ public Response getPayments(@PathParam("invoiceId") final String invoiceId, @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode, @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo, @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, InvoiceApiException { - final TenantContext tenantContext = context.createContext(request); + final TenantContext tenantContext = context.createContext(request); final Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId), tenantContext); // Extract unique set of paymentId for this invoice @@ -552,19 +563,22 @@ public UUID apply(final InvoicePayment input) { return input.getPaymentId(); } })); + if (invoicePaymentIds.isEmpty()) { + return Response.status(Status.OK).entity(ImmutableList.of()).build(); + } final List payments = new ArrayList(); for (final UUID paymentId : invoicePaymentIds) { final Payment payment = paymentApi.getPayment(paymentId, withPluginInfo, ImmutableList.of(), tenantContext); payments.add(payment); } - final List result = new ArrayList(payments.size()); - if (payments.isEmpty()) { - return Response.status(Status.OK).entity(result).build(); - } - for (final Payment cur : payments) { - result.add(new InvoicePaymentJson(cur, invoice.getId(), null)); - } + + final Iterable result = INVOICE_PAYMENT_ORDERING.sortedCopy(Iterables.transform(payments, new Function() { + @Override + public InvoicePaymentJson apply(final Payment input) { + return new InvoicePaymentJson(input, invoice.getId(), null); + } + })); return Response.status(Status.OK).entity(result).build(); } From 955bc6f3835489d10228ee7ba4532af149c26824 Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Mon, 14 Sep 2015 18:25:10 +0000 Subject: [PATCH 082/137] pom.xml: updates for release 0.15.4 See https://github.com/killbill/killbill/issues?q=milestone%3ARelease-0.15.4+is%3Aclosed Signed-off-by: Kill Bill core team --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 4118402711..b9c3523b8e 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +0.15.4 + See https://github.com/killbill/killbill/issues?q=milestone%3ARelease-0.15.4+is%3Aclosed + 0.15.3 Release for milestone 0.15.3: https://github.com/killbill/killbill/issues?q=is%3Aissue+milestone%3ARelease-0.15.3+is%3Aclosed From edb6f346e36b4ea06eb0b6147211684f1922528b Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Mon, 14 Sep 2015 18:31:05 +0000 Subject: [PATCH 083/137] [maven-release-plugin] prepare release killbill-0.15.4 --- account/pom.xml | 2 +- api/pom.xml | 2 +- beatrix/pom.xml | 2 +- catalog/pom.xml | 2 +- currency/pom.xml | 2 +- entitlement/pom.xml | 2 +- invoice/pom.xml | 2 +- jaxrs/pom.xml | 2 +- junction/pom.xml | 2 +- overdue/pom.xml | 2 +- payment/pom.xml | 2 +- pom.xml | 2 +- profiles/killbill/pom.xml | 2 +- profiles/killpay/pom.xml | 2 +- profiles/pom.xml | 2 +- subscription/pom.xml | 2 +- tenant/pom.xml | 2 +- usage/pom.xml | 2 +- util/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/account/pom.xml b/account/pom.xml index 44b911abcc..9f7c8bc838 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-account diff --git a/api/pom.xml b/api/pom.xml index 5b8aad5904..4e493fc5cc 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-internal-api diff --git a/beatrix/pom.xml b/beatrix/pom.xml index c6bdbdac1d..6fb1d1712d 100644 --- a/beatrix/pom.xml +++ b/beatrix/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-beatrix diff --git a/catalog/pom.xml b/catalog/pom.xml index e6ad7c8f0d..905307b42a 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-catalog diff --git a/currency/pom.xml b/currency/pom.xml index 55e978a8d6..b10976a3b3 100644 --- a/currency/pom.xml +++ b/currency/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-currency diff --git a/entitlement/pom.xml b/entitlement/pom.xml index 7895360c3b..27838b6c10 100644 --- a/entitlement/pom.xml +++ b/entitlement/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-entitlement diff --git a/invoice/pom.xml b/invoice/pom.xml index b1460fe706..986e56eda8 100644 --- a/invoice/pom.xml +++ b/invoice/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-invoice diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index fbb674e131..45f66471af 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-jaxrs diff --git a/junction/pom.xml b/junction/pom.xml index 2f403da626..4348452f3e 100644 --- a/junction/pom.xml +++ b/junction/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-junction diff --git a/overdue/pom.xml b/overdue/pom.xml index 03b322c471..32baa39c46 100644 --- a/overdue/pom.xml +++ b/overdue/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-overdue diff --git a/payment/pom.xml b/payment/pom.xml index ef45525095..9903ac9030 100644 --- a/payment/pom.xml +++ b/payment/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-payment diff --git a/pom.xml b/pom.xml index 4aa4e0c02a..c9ee93652a 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 0.43 killbill - 0.15.4-SNAPSHOT + 0.15.4 pom killbill Library for managing recurring subscriptions and the associated billing diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml index 78f963d987..4c1e990565 100644 --- a/profiles/killbill/pom.xml +++ b/profiles/killbill/pom.xml @@ -21,7 +21,7 @@ killbill-profiles org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-profiles-killbill diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml index 231f34320b..547f58f77e 100644 --- a/profiles/killpay/pom.xml +++ b/profiles/killpay/pom.xml @@ -20,7 +20,7 @@ killbill-profiles org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-profiles-killpay diff --git a/profiles/pom.xml b/profiles/pom.xml index 88e8cd00ee..a6560e78da 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-profiles diff --git a/subscription/pom.xml b/subscription/pom.xml index cd54166128..ab01041497 100644 --- a/subscription/pom.xml +++ b/subscription/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-subscription diff --git a/tenant/pom.xml b/tenant/pom.xml index a8d2a80a6b..d7b7ee77de 100644 --- a/tenant/pom.xml +++ b/tenant/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-tenant diff --git a/usage/pom.xml b/usage/pom.xml index 16205bde4f..8d4cd6f1b0 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-usage diff --git a/util/pom.xml b/util/pom.xml index bfc65cee27..7d616c7c77 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.4-SNAPSHOT + 0.15.4 ../pom.xml killbill-util From 67ba21df3341e22e0dffe0e82b88d6f0b5d9cbcc Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Mon, 14 Sep 2015 18:31:08 +0000 Subject: [PATCH 084/137] [maven-release-plugin] prepare for next development iteration --- account/pom.xml | 2 +- api/pom.xml | 2 +- beatrix/pom.xml | 2 +- catalog/pom.xml | 2 +- currency/pom.xml | 2 +- entitlement/pom.xml | 2 +- invoice/pom.xml | 2 +- jaxrs/pom.xml | 2 +- junction/pom.xml | 2 +- overdue/pom.xml | 2 +- payment/pom.xml | 2 +- pom.xml | 2 +- profiles/killbill/pom.xml | 2 +- profiles/killpay/pom.xml | 2 +- profiles/pom.xml | 2 +- subscription/pom.xml | 2 +- tenant/pom.xml | 2 +- usage/pom.xml | 2 +- util/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/account/pom.xml b/account/pom.xml index 9f7c8bc838..d1e21ac30f 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-account diff --git a/api/pom.xml b/api/pom.xml index 4e493fc5cc..d574f92ffc 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-internal-api diff --git a/beatrix/pom.xml b/beatrix/pom.xml index 6fb1d1712d..3b7e77b34d 100644 --- a/beatrix/pom.xml +++ b/beatrix/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-beatrix diff --git a/catalog/pom.xml b/catalog/pom.xml index 905307b42a..ac433c25ce 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-catalog diff --git a/currency/pom.xml b/currency/pom.xml index b10976a3b3..4857b6c0d8 100644 --- a/currency/pom.xml +++ b/currency/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-currency diff --git a/entitlement/pom.xml b/entitlement/pom.xml index 27838b6c10..8d89eb7b9f 100644 --- a/entitlement/pom.xml +++ b/entitlement/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-entitlement diff --git a/invoice/pom.xml b/invoice/pom.xml index 986e56eda8..c8b479a089 100644 --- a/invoice/pom.xml +++ b/invoice/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-invoice diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index 45f66471af..72ad4b5aa7 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-jaxrs diff --git a/junction/pom.xml b/junction/pom.xml index 4348452f3e..603b78ee7e 100644 --- a/junction/pom.xml +++ b/junction/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-junction diff --git a/overdue/pom.xml b/overdue/pom.xml index 32baa39c46..67d1ec6828 100644 --- a/overdue/pom.xml +++ b/overdue/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-overdue diff --git a/payment/pom.xml b/payment/pom.xml index 9903ac9030..5e3cc5095d 100644 --- a/payment/pom.xml +++ b/payment/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-payment diff --git a/pom.xml b/pom.xml index c9ee93652a..9947cb5fc5 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 0.43 killbill - 0.15.4 + 0.15.5-SNAPSHOT pom killbill Library for managing recurring subscriptions and the associated billing diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml index 4c1e990565..67dc2905c1 100644 --- a/profiles/killbill/pom.xml +++ b/profiles/killbill/pom.xml @@ -21,7 +21,7 @@ killbill-profiles org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-profiles-killbill diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml index 547f58f77e..ae6bc6e667 100644 --- a/profiles/killpay/pom.xml +++ b/profiles/killpay/pom.xml @@ -20,7 +20,7 @@ killbill-profiles org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-profiles-killpay diff --git a/profiles/pom.xml b/profiles/pom.xml index a6560e78da..920edfd80c 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-profiles diff --git a/subscription/pom.xml b/subscription/pom.xml index ab01041497..0094e78512 100644 --- a/subscription/pom.xml +++ b/subscription/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-subscription diff --git a/tenant/pom.xml b/tenant/pom.xml index d7b7ee77de..a0e0af0455 100644 --- a/tenant/pom.xml +++ b/tenant/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-tenant diff --git a/usage/pom.xml b/usage/pom.xml index 8d4cd6f1b0..20a2c86a55 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-usage diff --git a/util/pom.xml b/util/pom.xml index 7d616c7c77..3caf4eba32 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.4 + 0.15.5-SNAPSHOT ../pom.xml killbill-util From 998000d8266f1abee83611e5e653fbef2af63b0a Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 16 Sep 2015 15:34:00 -0700 Subject: [PATCH 085/137] account: Make sure account non null fields have a default value even if the provided json does not set their value --- .../org/killbill/billing/jaxrs/json/AccountJson.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java index 91763c01ca..cc0606528b 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java @@ -28,10 +28,13 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountData; import org.killbill.billing.catalog.api.Currency; +import org.killbill.billing.util.UUIDs; import org.killbill.billing.util.audit.AccountAuditLogs; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; import com.google.common.base.Strings; import com.wordnik.swagger.annotations.ApiModelProperty; @@ -116,9 +119,9 @@ public AccountJson(@JsonProperty("accountId") final String accountId, this.accountBalance = accountBalance; this.externalKey = externalKey; this.accountId = accountId; - this.name = name; - this.firstNameLength = firstNameLength; - this.email = email; + this.name = MoreObjects.firstNonNull(name, UUIDs.randomUUID().toString()); + this.firstNameLength = MoreObjects.firstNonNull(firstNameLength, this.name.length()); + this.email = MoreObjects.firstNonNull(email, ""); this.billCycleDayLocal = billCycleDayLocal; this.currency = currency; this.paymentMethodId = paymentMethodId; @@ -133,7 +136,7 @@ public AccountJson(@JsonProperty("accountId") final String accountId, this.locale = locale; this.phone = phone; this.isMigrated = isMigrated; - this.isNotifiedForInvoices = isNotifiedForInvoices; + this.isNotifiedForInvoices = isNotifiedForInvoices != null ? isNotifiedForInvoices : false; this.accountCBA = accountCBA; } From ce2ed36a0cff6e970a0e8138e7c161916cd11902 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 16 Sep 2015 16:03:27 -0700 Subject: [PATCH 086/137] jaxrs: Fix broken test because of commit 8b8c444296bfa2d65ec1bec3e5d5c38e44dfed28 --- .../src/test/java/org/killbill/billing/jaxrs/TestAccount.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java index 60072e821d..0d20588b03 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java @@ -55,8 +55,8 @@ public void testEmptyAccount() throws Exception { final Account account = killBillClient.createAccount(emptyAccount, createdBy, reason, comment); Assert.assertNotNull(account.getExternalKey()); - Assert.assertNull(account.getName()); - Assert.assertNull(account.getEmail()); + Assert.assertNotNull(account.getName()); + Assert.assertNotNull(account.getEmail()); } @Test(groups = "slow", description = "Verify external key is unique") From d33e6ef84b9794851ea89da084ca0b3c511d1c8e Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Wed, 16 Sep 2015 23:41:47 +0000 Subject: [PATCH 087/137] pom.xml: updates for release 0.15.5 Fix with issue with payment combo call where default account fields are not part of the request Signed-off-by: Kill Bill core team --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index b9c3523b8e..0fd713607b 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +0.15.5 + Fix with issue with payment combo call where default account fields are not part of the request + 0.15.4 See https://github.com/killbill/killbill/issues?q=milestone%3ARelease-0.15.4+is%3Aclosed From 445aaabfafb0f50791efc2ee6cac8e91eff3af16 Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Wed, 16 Sep 2015 23:47:43 +0000 Subject: [PATCH 088/137] [maven-release-plugin] prepare release killbill-0.15.5 --- account/pom.xml | 2 +- api/pom.xml | 2 +- beatrix/pom.xml | 2 +- catalog/pom.xml | 2 +- currency/pom.xml | 2 +- entitlement/pom.xml | 2 +- invoice/pom.xml | 2 +- jaxrs/pom.xml | 2 +- junction/pom.xml | 2 +- overdue/pom.xml | 2 +- payment/pom.xml | 2 +- pom.xml | 2 +- profiles/killbill/pom.xml | 2 +- profiles/killpay/pom.xml | 2 +- profiles/pom.xml | 2 +- subscription/pom.xml | 2 +- tenant/pom.xml | 2 +- usage/pom.xml | 2 +- util/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/account/pom.xml b/account/pom.xml index d1e21ac30f..dd13589b37 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-account diff --git a/api/pom.xml b/api/pom.xml index d574f92ffc..759d2efbd7 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-internal-api diff --git a/beatrix/pom.xml b/beatrix/pom.xml index 3b7e77b34d..1d83e5ad9c 100644 --- a/beatrix/pom.xml +++ b/beatrix/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-beatrix diff --git a/catalog/pom.xml b/catalog/pom.xml index ac433c25ce..1b6b89bbe9 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-catalog diff --git a/currency/pom.xml b/currency/pom.xml index 4857b6c0d8..390dab51f7 100644 --- a/currency/pom.xml +++ b/currency/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-currency diff --git a/entitlement/pom.xml b/entitlement/pom.xml index 8d89eb7b9f..e9880f9917 100644 --- a/entitlement/pom.xml +++ b/entitlement/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-entitlement diff --git a/invoice/pom.xml b/invoice/pom.xml index c8b479a089..29b60e4450 100644 --- a/invoice/pom.xml +++ b/invoice/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-invoice diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index 72ad4b5aa7..e58316c263 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-jaxrs diff --git a/junction/pom.xml b/junction/pom.xml index 603b78ee7e..9ec4f52119 100644 --- a/junction/pom.xml +++ b/junction/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-junction diff --git a/overdue/pom.xml b/overdue/pom.xml index 67d1ec6828..4808e75d64 100644 --- a/overdue/pom.xml +++ b/overdue/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-overdue diff --git a/payment/pom.xml b/payment/pom.xml index 5e3cc5095d..90942f8c8e 100644 --- a/payment/pom.xml +++ b/payment/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-payment diff --git a/pom.xml b/pom.xml index 9947cb5fc5..baa7205aa7 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 0.43 killbill - 0.15.5-SNAPSHOT + 0.15.5 pom killbill Library for managing recurring subscriptions and the associated billing diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml index 67dc2905c1..5ece986eee 100644 --- a/profiles/killbill/pom.xml +++ b/profiles/killbill/pom.xml @@ -21,7 +21,7 @@ killbill-profiles org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-profiles-killbill diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml index ae6bc6e667..6e136ef028 100644 --- a/profiles/killpay/pom.xml +++ b/profiles/killpay/pom.xml @@ -20,7 +20,7 @@ killbill-profiles org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-profiles-killpay diff --git a/profiles/pom.xml b/profiles/pom.xml index 920edfd80c..93be682601 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-profiles diff --git a/subscription/pom.xml b/subscription/pom.xml index 0094e78512..c0194142d8 100644 --- a/subscription/pom.xml +++ b/subscription/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-subscription diff --git a/tenant/pom.xml b/tenant/pom.xml index a0e0af0455..cb889e0865 100644 --- a/tenant/pom.xml +++ b/tenant/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-tenant diff --git a/usage/pom.xml b/usage/pom.xml index 20a2c86a55..df8f499b61 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-usage diff --git a/util/pom.xml b/util/pom.xml index 3caf4eba32..7e596d9808 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.5-SNAPSHOT + 0.15.5 ../pom.xml killbill-util From c23960fd2352ec2489ace6fe2071ea42d3cfea27 Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Wed, 16 Sep 2015 23:47:47 +0000 Subject: [PATCH 089/137] [maven-release-plugin] prepare for next development iteration --- account/pom.xml | 2 +- api/pom.xml | 2 +- beatrix/pom.xml | 2 +- catalog/pom.xml | 2 +- currency/pom.xml | 2 +- entitlement/pom.xml | 2 +- invoice/pom.xml | 2 +- jaxrs/pom.xml | 2 +- junction/pom.xml | 2 +- overdue/pom.xml | 2 +- payment/pom.xml | 2 +- pom.xml | 2 +- profiles/killbill/pom.xml | 2 +- profiles/killpay/pom.xml | 2 +- profiles/pom.xml | 2 +- subscription/pom.xml | 2 +- tenant/pom.xml | 2 +- usage/pom.xml | 2 +- util/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/account/pom.xml b/account/pom.xml index dd13589b37..5bb5dc2091 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-account diff --git a/api/pom.xml b/api/pom.xml index 759d2efbd7..3361c9b190 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-internal-api diff --git a/beatrix/pom.xml b/beatrix/pom.xml index 1d83e5ad9c..f5f117e40b 100644 --- a/beatrix/pom.xml +++ b/beatrix/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-beatrix diff --git a/catalog/pom.xml b/catalog/pom.xml index 1b6b89bbe9..cea261f679 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-catalog diff --git a/currency/pom.xml b/currency/pom.xml index 390dab51f7..473298ebf1 100644 --- a/currency/pom.xml +++ b/currency/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-currency diff --git a/entitlement/pom.xml b/entitlement/pom.xml index e9880f9917..b64dd693e9 100644 --- a/entitlement/pom.xml +++ b/entitlement/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-entitlement diff --git a/invoice/pom.xml b/invoice/pom.xml index 29b60e4450..bcf40cb236 100644 --- a/invoice/pom.xml +++ b/invoice/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-invoice diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index e58316c263..5ba634e6c4 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-jaxrs diff --git a/junction/pom.xml b/junction/pom.xml index 9ec4f52119..bf62b8995c 100644 --- a/junction/pom.xml +++ b/junction/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-junction diff --git a/overdue/pom.xml b/overdue/pom.xml index 4808e75d64..9119547532 100644 --- a/overdue/pom.xml +++ b/overdue/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-overdue diff --git a/payment/pom.xml b/payment/pom.xml index 90942f8c8e..0b9df0b938 100644 --- a/payment/pom.xml +++ b/payment/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-payment diff --git a/pom.xml b/pom.xml index baa7205aa7..a58bd917e9 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 0.43 killbill - 0.15.5 + 0.15.6-SNAPSHOT pom killbill Library for managing recurring subscriptions and the associated billing diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml index 5ece986eee..edaf6b1151 100644 --- a/profiles/killbill/pom.xml +++ b/profiles/killbill/pom.xml @@ -21,7 +21,7 @@ killbill-profiles org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-profiles-killbill diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml index 6e136ef028..bead5a09cc 100644 --- a/profiles/killpay/pom.xml +++ b/profiles/killpay/pom.xml @@ -20,7 +20,7 @@ killbill-profiles org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-profiles-killpay diff --git a/profiles/pom.xml b/profiles/pom.xml index 93be682601..e05b9df056 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-profiles diff --git a/subscription/pom.xml b/subscription/pom.xml index c0194142d8..7abe53e34f 100644 --- a/subscription/pom.xml +++ b/subscription/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-subscription diff --git a/tenant/pom.xml b/tenant/pom.xml index cb889e0865..b9ebd09d22 100644 --- a/tenant/pom.xml +++ b/tenant/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-tenant diff --git a/usage/pom.xml b/usage/pom.xml index df8f499b61..b2d4475dce 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-usage diff --git a/util/pom.xml b/util/pom.xml index 7e596d9808..e5acdd9616 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.5 + 0.15.6-SNAPSHOT ../pom.xml killbill-util From 1c1fdf63b08ce2df4d9d7e2240ba2135874f561b Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 17 Sep 2015 16:46:55 -0700 Subject: [PATCH 090/137] payment: Make payment control api consistent with row payment api (by not throwing exception on payment failure and returning payment object) --- .../PluginControlPaymentAutomatonRunner.java | 14 +++---- .../billing/payment/TestRetryService.java | 30 +++------------ .../billing/payment/api/TestPaymentApi.java | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java index b15dcdbbfe..dd4d222bbe 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java @@ -66,6 +66,7 @@ import org.killbill.commons.locker.GlobalLocker; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED; @@ -119,15 +120,14 @@ public Payment run(final State state, final boolean isApiPayment, final Transact state.runOperation(paymentControlStateMachineHelper.getOperation(), callback, enteringStateCallback, leavingStateCallback); } catch (final MissingEntryException e) { - throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), "")); + throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), "")); } catch (final OperationException e) { - if (e.getCause() == null) { - // Unclear if we should check whether there is a result that was set and return that result. - throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), "")); - } else if (e.getCause() instanceof PaymentApiException) { + if (e.getCause() instanceof PaymentApiException) { throw (PaymentApiException) e.getCause(); - } else { - throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), "")); + // If the result is set (and cause is null), that means we created a Payment but the associated transaction status is 'XXX_FAILURE', + // we don't throw, and return the failed Payment instead to be consistent with what happens when we don't go through control api. + } else if (e.getCause() != null || paymentStateContext.getResult() == null) { + throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), "")); } } return paymentStateContext.getResult(); diff --git a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java index 33b1484f36..cbc23e577e 100644 --- a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java +++ b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java @@ -159,16 +159,10 @@ public void testFailedPaymentWithOneSuccessfulRetry() throws Exception { Currency.USD)); setPaymentFailure(FailureType.PAYMENT_FAILURE); - boolean failed = false; final String paymentExternalKey = UUID.randomUUID().toString(); final String transactionExternalKey = UUID.randomUUID().toString(); - try { - pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey, - createPropertiesForInvoice(invoice), ImmutableList.of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext); - } catch (final PaymentApiException e) { - failed = true; - } - assertTrue(failed); + pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey, + createPropertiesForInvoice(invoice), ImmutableList.of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext); Payment payment = getPaymentForExternalKey(paymentExternalKey); List attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext); @@ -242,16 +236,10 @@ public void testFailedPaymentWithLastRetrySuccess() throws Exception { Currency.USD)); setPaymentFailure(FailureType.PAYMENT_FAILURE); - boolean failed = false; final String paymentExternalKey = UUID.randomUUID().toString(); final String transactionExternalKey = UUID.randomUUID().toString(); - try { - pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey, - createPropertiesForInvoice(invoice), ImmutableList.of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext); - } catch (final PaymentApiException e) { - failed = true; - } - assertTrue(failed); + pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey, + createPropertiesForInvoice(invoice), ImmutableList.of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext); Payment payment = getPaymentForExternalKey(paymentExternalKey); List attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext); @@ -335,16 +323,10 @@ public void testAbortedPayment() throws Exception { Currency.USD)); setPaymentFailure(FailureType.PAYMENT_FAILURE); - boolean failed = false; final String paymentExternalKey = UUID.randomUUID().toString(); final String transactionExternalKey = UUID.randomUUID().toString(); - try { - pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey, - createPropertiesForInvoice(invoice), ImmutableList.of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext); - } catch (final PaymentApiException e) { - failed = true; - } - assertTrue(failed); + pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey, + createPropertiesForInvoice(invoice), ImmutableList.of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext); Payment payment = getPaymentForExternalKey(paymentExternalKey); List attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext); diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java index 0960badf90..f843590474 100644 --- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java +++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java @@ -118,6 +118,42 @@ public void testCreateSuccessPurchase() throws PaymentApiException { assertNull(payment.getTransactions().get(0).getGatewayErrorCode()); } + @Test(groups = "slow") + public void testCreateFailedPurchase() throws PaymentApiException { + + final BigDecimal requestedAmount = BigDecimal.TEN; + + final String paymentExternalKey = "ohhhh"; + final String transactionExternalKey = "naaahhh"; + + mockPaymentProviderPlugin.makeNextPaymentFailWithError(); + + final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey, + ImmutableList.of(), callContext); + + assertEquals(payment.getExternalKey(), paymentExternalKey); + assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId()); + assertEquals(payment.getAccountId(), account.getId()); + assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0); + assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0); + assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0); + assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0); + assertEquals(payment.getCurrency(), Currency.AED); + + assertEquals(payment.getTransactions().size(), 1); + assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey); + assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId()); + assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0); + assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED); + assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); + assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED); + + assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE); + assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE); + assertNull(payment.getTransactions().get(0).getGatewayErrorMsg()); + assertNull(payment.getTransactions().get(0).getGatewayErrorCode()); + } + @Test(groups = "slow") public void testCreateSuccessAuthCapture() throws PaymentApiException { @@ -311,6 +347,7 @@ public void testCreateFailedPurchaseWithPaymentControl() throws PaymentApiExcept paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey, createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext); } catch (final PaymentApiException expected) { + assertTrue(true); } From afe8738a89a7fab78c3bb2bcf2d6557ea26942f6 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 17 Sep 2015 16:50:49 -0700 Subject: [PATCH 091/137] jaxrs: revert change for 998000d8266f1abee83611e5e653fbef2af63b0a since name, email, isNotifiedForInvoice fields are not required (relaxed constraints at the DB level) --- .../org/killbill/billing/jaxrs/json/AccountJson.java | 11 ++++------- .../java/org/killbill/billing/jaxrs/TestAccount.java | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java index cc0606528b..91763c01ca 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java @@ -28,13 +28,10 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountData; import org.killbill.billing.catalog.api.Currency; -import org.killbill.billing.util.UUIDs; import org.killbill.billing.util.audit.AccountAuditLogs; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; import com.google.common.base.Strings; import com.wordnik.swagger.annotations.ApiModelProperty; @@ -119,9 +116,9 @@ public AccountJson(@JsonProperty("accountId") final String accountId, this.accountBalance = accountBalance; this.externalKey = externalKey; this.accountId = accountId; - this.name = MoreObjects.firstNonNull(name, UUIDs.randomUUID().toString()); - this.firstNameLength = MoreObjects.firstNonNull(firstNameLength, this.name.length()); - this.email = MoreObjects.firstNonNull(email, ""); + this.name = name; + this.firstNameLength = firstNameLength; + this.email = email; this.billCycleDayLocal = billCycleDayLocal; this.currency = currency; this.paymentMethodId = paymentMethodId; @@ -136,7 +133,7 @@ public AccountJson(@JsonProperty("accountId") final String accountId, this.locale = locale; this.phone = phone; this.isMigrated = isMigrated; - this.isNotifiedForInvoices = isNotifiedForInvoices != null ? isNotifiedForInvoices : false; + this.isNotifiedForInvoices = isNotifiedForInvoices; this.accountCBA = accountCBA; } diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java index 0d20588b03..60072e821d 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java @@ -55,8 +55,8 @@ public void testEmptyAccount() throws Exception { final Account account = killBillClient.createAccount(emptyAccount, createdBy, reason, comment); Assert.assertNotNull(account.getExternalKey()); - Assert.assertNotNull(account.getName()); - Assert.assertNotNull(account.getEmail()); + Assert.assertNull(account.getName()); + Assert.assertNull(account.getEmail()); } @Test(groups = "slow", description = "Verify external key is unique") From 364ce2bac7252d0d3c671d9559fc460118d9b9a1 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Thu, 17 Sep 2015 19:24:34 -0700 Subject: [PATCH 092/137] jaxrs: fix payment test after semantics change from 1c1fdf63b08ce2df4d9d7e2240ba2135874f561b --- .../java/org/killbill/billing/jaxrs/TestChargeback.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java index ab4fb282b4..50e2a2d571 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java @@ -67,12 +67,7 @@ public void testMultipleChargeback() throws Exception { } // Last attempt should fail because this is more than the Payment - try { - killBillClient.createInvoicePaymentChargeback(input, createdBy, reason, comment); - fail(); - } catch (final KillBillClientException e) { - } - + final InvoicePayment foo = killBillClient.createInvoicePaymentChargeback(input, createdBy, reason, comment); final List payments = killBillClient.getInvoicePaymentsForAccount(payment.getAccountId()); final List transactions = getPaymentTransactions(payments, TransactionType.CHARGEBACK.toString()); Assert.assertEquals(transactions.size(), 5); From ab2ba000a10fbe94f3384a34f58f9712f0ee7cb2 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 18 Sep 2015 10:49:36 -0700 Subject: [PATCH 093/137] jaxrs: filter out plugin properties for which the value is null --- .../billing/jaxrs/resources/JaxRsResourceBase.java | 7 ++++++- .../jaxrs/resources/TestJaxRsResourceBase.java | 12 +++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java index b46b836bdf..254570143e 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java @@ -367,9 +367,14 @@ protected Iterable extractPluginProperties(@Nullable final Itera for (final String pluginProperty : pluginProperties) { final List property = ImmutableList.copyOf(pluginProperty.split("=")); + // Skip entries for which there is no value + if (property.size() == 1) { + continue; + } + final String key = property.get(0); // Should we URL decode the value? - String value = property.size() == 1 ? null : Joiner.on("=").join(property.subList(1, property.size())); + String value = Joiner.on("=").join(property.subList(1, property.size())); if (pluginProperty.endsWith("=")) { value += "="; } diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/resources/TestJaxRsResourceBase.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/resources/TestJaxRsResourceBase.java index 01885d890f..ecf1d4401c 100644 --- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/resources/TestJaxRsResourceBase.java +++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/resources/TestJaxRsResourceBase.java @@ -51,7 +51,17 @@ public void testExtractPluginProperties() throws Exception { Assert.assertEquals(pluginProperties.get(4).getValue(), "2020"); } - private static final class JaxRsResourceBaseTest extends JaxRsResourceBase { + @Test(groups = "fast") + public void testExtractPluginPropertiesWithNullProperty() throws Exception { + final List pluginPropertiesString = ImmutableList.of("foo=", + "bar=ttt"); + final List pluginProperties = ImmutableList.copyOf(base.extractPluginProperties(pluginPropertiesString)); + Assert.assertEquals(pluginProperties.size(), 1); + Assert.assertEquals(pluginProperties.get(0).getKey(), "bar"); + Assert.assertEquals(pluginProperties.get(0).getValue(), "ttt"); + } + + private static final class JaxRsResourceBaseTest extends JaxRsResourceBase { public JaxRsResourceBaseTest() { super(null, null, null, null, null, null, null, null); From 6f898ff9e9113e8b75ef93aae1f5c29d57715576 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Fri, 18 Sep 2015 23:18:19 -0700 Subject: [PATCH 094/137] Combo auth returns 404 if can't find payment method. --- .../jaxrs/resources/ComboPaymentResource.java | 2 ++ .../killbill/billing/jaxrs/TestPayment.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java index a372150c52..e30864195e 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java @@ -23,6 +23,7 @@ import javax.annotation.Nullable; import javax.inject.Inject; +import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountUserApi; @@ -93,6 +94,7 @@ public boolean apply(final PaymentMethod input) { })) { return match; } + throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, match); } // If we were specified a paymentMethod externalKey and we find it, we return it diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java index 1cfdb15050..22a5996f03 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java @@ -193,6 +193,24 @@ public void testComboAuthorization() throws Exception { voidTransactionExternalKey, null, "VOID", "SUCCESS"); } + @Test(groups = "slow") + public void testComboAuthorizationInvalidPaymentMethod() throws Exception { + final Account accountJson = getAccount(); + accountJson.setAccountId(null); + + final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail(); + info.setProperties(null); + + final UUID paymentMethodId = UUID.randomUUID(); + final PaymentMethod paymentMethodJson = new PaymentMethod(paymentMethodId, null, null, true, PLUGIN_NAME, info); + + final ComboPaymentTransaction comboPaymentTransaction = new ComboPaymentTransaction(accountJson, paymentMethodJson, null, ImmutableList.of(), ImmutableList.of()); + + final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.of(), createdBy, reason, comment); + // Client returns null in case of a 404 + Assert.assertNull(payment); + } + private void testCreateRetrievePayment(final Account account, @Nullable final UUID paymentMethodId, final String paymentExternalKey, final int paymentNb) throws Exception { // Authorization From d978ab1638353f2d72d27a823429c2f23465ea2b Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sat, 19 Sep 2015 16:40:39 -0700 Subject: [PATCH 095/137] jaxrs: enhance Tag model to also return the objectId (matching the objectType) --- .../org/killbill/billing/jaxrs/json/TagJson.java | 15 ++++++++++++++- pom.xml | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/TagJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/TagJson.java index 0e8233d772..16dc757a36 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/TagJson.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/TagJson.java @@ -36,24 +36,28 @@ public class TagJson extends JsonBase { @ApiModelProperty(dataType = "org.killbill.billing.ObjectType") private final ObjectType objectType; @ApiModelProperty(dataType = "java.util.UUID") + private final String objectId; + @ApiModelProperty(dataType = "java.util.UUID") private final String tagDefinitionId; private final String tagDefinitionName; @JsonCreator public TagJson(@JsonProperty("tagId") final String tagId, @JsonProperty("objectType") final ObjectType objectType, + @JsonProperty("objectId") final String objectId, @JsonProperty("tagDefinitionId") final String tagDefinitionId, @JsonProperty("tagDefinitionName") final String tagDefinitionName, @JsonProperty("auditLogs") @Nullable final List auditLogs) { super(auditLogs); this.tagId = tagId; this.objectType = objectType; + this.objectId = objectId; this.tagDefinitionId = tagDefinitionId; this.tagDefinitionName = tagDefinitionName; } public TagJson(final Tag tag, final TagDefinition tagDefinition, @Nullable final List auditLogs) { - this(tag.getId().toString(), tag.getObjectType(), tagDefinition.getId().toString(), tagDefinition.getName(), toAuditLogJson(auditLogs)); + this(tag.getId().toString(), tag.getObjectType(), tag.getObjectId().toString(), tagDefinition.getId().toString(), tagDefinition.getName(), toAuditLogJson(auditLogs)); } public String getTagId() { @@ -72,11 +76,16 @@ public String getTagDefinitionName() { return tagDefinitionName; } + public String getObjectId() { + return objectId; + } + @Override public String toString() { final StringBuilder sb = new StringBuilder("TagJson{"); sb.append("tagId='").append(tagId).append('\''); sb.append(", objectType=").append(objectType); + sb.append(", objectId=").append(objectId); sb.append(", tagDefinitionId='").append(tagDefinitionId).append('\''); sb.append(", tagDefinitionName='").append(tagDefinitionName).append('\''); sb.append('}'); @@ -100,6 +109,9 @@ public boolean equals(final Object o) { if (tagDefinitionId != null ? !tagDefinitionId.equals(tagJson.tagDefinitionId) : tagJson.tagDefinitionId != null) { return false; } + if (objectId != null ? !objectId.equals(tagJson.objectId) : tagJson.objectId != null) { + return false; + } if (tagDefinitionName != null ? !tagDefinitionName.equals(tagJson.tagDefinitionName) : tagJson.tagDefinitionName != null) { return false; } @@ -115,6 +127,7 @@ public int hashCode() { int result = tagId != null ? tagId.hashCode() : 0; result = 31 * result + (objectType != null ? objectType.hashCode() : 0); result = 31 * result + (tagDefinitionId != null ? tagDefinitionId.hashCode() : 0); + result = 31 * result + (objectId != null ? objectId.hashCode() : 0); result = 31 * result + (tagDefinitionName != null ? tagDefinitionName.hashCode() : 0); return result; } diff --git a/pom.xml b/pom.xml index a58bd917e9..572a477da2 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.43 + 0.44 killbill 0.15.6-SNAPSHOT From 95a4ed0cc1f263f7ee1ff38d02fc0d5b4c4801bb Mon Sep 17 00:00:00 2001 From: David Xie Date: Mon, 21 Sep 2015 22:45:37 +0800 Subject: [PATCH 096/137] Add getopt check for db-helper --- bin/db-helper | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/bin/db-helper b/bin/db-helper index 4f5130c674..c642b1c666 100755 --- a/bin/db-helper +++ b/bin/db-helper @@ -43,6 +43,17 @@ CLEAN_FILE= # Egrep like for skipping some modules until they are ready SKIP="(server)" + +# test if user is running gnu-getopt +TEST=`getopt -o "a:" -l "action:" -- --action dump` +if [ "$TEST" != " --action 'dump' --" ]; then + echo "You are not using gnu-getopt or latest getopt." + echo "For Mac OS X, please upgrade 'getopt' to 'gnu-getopt'," + echo "For Linux, please upgrade 'getopt'." + exit +fi + + ARGS=`getopt -o "a:d:h:u:p:t:f:" -l "action:,driver:,database:,host:,user:,password:,test:,file:,port:,help" -n "db-helper" -- "$@"` eval set -- "${ARGS}" @@ -99,7 +110,7 @@ function create_clean_file() { local tables=`cat $ddl_file | grep -i "create table" | awk ' { print $3 } '` local tmp="/tmp/clean-$DATABASE.$$" - echo "/*! use $DATABASE; */" >> $tmp + echo "/*! USE $DATABASE */;" >> $tmp echo "" >> $tmp for t in $tables; do echo "truncate $t;" >> $tmp @@ -121,7 +132,7 @@ function create_ddl_file() { if [ $DRIVER == "postgres" ]; then cat util/src/main/resources/org/killbill/billing/util/ddl-postgresql.sql > $tmp fi - echo "/*! use $DATABASE; */" >> $tmp + echo "/*! USE $DATABASE */;" >> $tmp echo "" >> $tmp for d in $ddls; do cat $d >> $tmp From b18a12c9d34c9b3ef5b22749e2e41dcd65fe238d Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 22 Sep 2015 19:59:34 -0700 Subject: [PATCH 097/137] server: Introduce a new system property to allow returning gzip http requests when the client asks for it (Accept-Encoding: gzip) --- .../server/listeners/KillbillGuiceListener.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java index 0b16384771..9f76e4f1b3 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java @@ -42,10 +42,13 @@ import com.google.common.collect.ImmutableMap; import com.google.inject.Module; import com.google.inject.servlet.ServletModule; +import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter; import com.wordnik.swagger.jaxrs.config.BeanConfig; public class KillbillGuiceListener extends KillbillPlatformGuiceListener { + private static final String ENABLE_HTTP_GZIP_RESPONSES = "org.killbill.server.http.gzip"; + private static final Logger logger = LoggerFactory.getLogger(KillbillGuiceListener.class); private static final String SWAGGER_PATH = "api-docs"; @@ -71,8 +74,12 @@ protected ServletModule getServletModule() { // c.s.j.s.w.g.AbstractWadlGeneratorGrammarGenerator - Couldn't find grammar element for class javax.ws.rs.core.Response builder.addJerseyParam("com.sun.jersey.config.feature.DisableWADL", "true"); - // The logging filter is still incompatible with the GZIP filter - //builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName()); + // In order to use the GZIPContentEncodingFilter, the jersey param "com.sun.jersey.config.feature.logging.DisableEntitylogging" + // must not be set to false. + if (Boolean.valueOf(System.getProperty(ENABLE_HTTP_GZIP_RESPONSES, "false"))) { + logger.info("Enable http gzip responses"); + builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName()); + } builder.addJerseyFilter(ProfilingContainerResponseFilter.class.getName()); builder.addJerseyFilter(RequestDataFilter.class.getName()); From 85704caade15abe62af49dc3bbc1ece5e9ae56e3 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 22 Sep 2015 20:01:28 -0700 Subject: [PATCH 098/137] start script: Configure by default the http gzip filter --- bin/start-server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/start-server b/bin/start-server index 4327f7af04..064ea1308b 100755 --- a/bin/start-server +++ b/bin/start-server @@ -30,7 +30,7 @@ DEBUG_OPTS_ECLIPSE=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,ad DEBUG_OPTS_ECLIPSE_WAIT=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=12345 " # Default JVM settings if unset -MAVEN_OPTS=${MAVEN_OPTS-"-Duser.timezone=GMT -Xms512m -Xmx1024m -XX:MaxPermSize=512m -XX:MaxDirectMemorySize=512m -XX:+UseConcMarkSweepGC"} +MAVEN_OPTS=${MAVEN_OPTS-"-Duser.timezone=GMT -Dorg.killbill.server.http.gzip=true -Xms512m -Xmx1024m -XX:MaxPermSize=512m -XX:MaxDirectMemorySize=512m -XX:+UseConcMarkSweepGC"} LOG="$SERVER/src/main/resources/logback.xml" LOG_DIR="$SERVER/logs" From 20291ec67171b35322deef480c56244e07701a58 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 23 Sep 2015 05:46:59 -0700 Subject: [PATCH 099/137] payment: allow plugin to set the payment method external key Support the use-case where the plugin creates the external key (for instance, when the payment method is created in a separate system or gateway). Signed-off-by: Pierre-Alexandre Meyer --- .../payment/core/PaymentMethodProcessor.java | 35 +++++++++++++++++-- .../core/TestPaymentMethodProcessorNoDB.java | 23 ++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java index 6368518e3f..2e644a2ff1 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java @@ -116,8 +116,15 @@ public PluginDispatcherReturnType doOperation() throws PaymentApiException pluginApi = getPaymentPluginApi(paymentPluginServiceName); pm = new DefaultPaymentMethod(paymentMethodExternalKey, account.getId(), paymentPluginServiceName, paymentMethodProps); pluginApi.addPaymentMethod(account.getId(), pm.getId(), paymentMethodProps, setDefault, properties, callContext); - final PaymentMethodModelDao pmModel = new PaymentMethodModelDao(pm.getId(), pm.getExternalKey(), pm.getCreatedDate(), pm.getUpdatedDate(), - pm.getAccountId(), pm.getPluginName(), pm.isActive()); + + final String actualPaymentMethodExternalKey = retrieveActualPaymentMethodExternalKey(account, pm, pluginApi, properties, callContext, context); + final PaymentMethodModelDao pmModel = new PaymentMethodModelDao(pm.getId(), + actualPaymentMethodExternalKey, + pm.getCreatedDate(), + pm.getUpdatedDate(), + pm.getAccountId(), + pm.getPluginName(), + pm.isActive()); paymentDao.insertPaymentMethod(pmModel, context); if (setDefault) { @@ -135,6 +142,30 @@ public PluginDispatcherReturnType doOperation() throws PaymentApiException uuidPluginNotificationDispatcher); } + private String retrieveActualPaymentMethodExternalKey(final Account account, final PaymentMethod pm, final PaymentPluginApi pluginApi, final Iterable properties, final TenantContext callContext, final InternalCallContext context) { + // If the user specified an external key, use it + if (pm.getExternalKey() != null) { + return pm.getExternalKey(); + } + + // Otherwise, check if the plugin sets an external payment method id + final PaymentMethodPlugin paymentMethodPlugin; + try { + paymentMethodPlugin = pluginApi.getPaymentMethodDetail(account.getId(), pm.getId(), properties, callContext); + } catch (final PaymentPluginApiException e) { + log.warn("Error retrieving payment method " + pm.getId() + " from plugin " + pm.getPluginName(), e); + return null; + } + + if (paymentMethodPlugin != null && paymentMethodPlugin.getExternalPaymentMethodId() != null) { + // An external payment method id is set but make sure it doesn't conflict with an existing one + final String externalKey = paymentMethodPlugin.getExternalPaymentMethodId(); + return paymentDao.getPaymentMethodByExternalKeyIncludedDeleted(externalKey, context) == null ? externalKey : null; + } else { + return null; + } + } + public List getPaymentMethods(final UUID accountId, final boolean withPluginInfo, final Iterable properties, final InternalTenantContext context) throws PaymentApiException { return getPaymentMethods(accountId, withPluginInfo, properties, buildTenantContext(context), context); } diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java index 522f3c465f..fd355dc697 100644 --- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java +++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java @@ -24,6 +24,7 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.payment.PaymentTestSuiteNoDB; import org.killbill.billing.payment.api.PaymentMethod; +import org.killbill.billing.payment.api.PaymentMethodPlugin; import org.killbill.billing.payment.api.PluginProperty; import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin; import org.mockito.Mockito; @@ -34,6 +35,28 @@ public class TestPaymentMethodProcessorNoDB extends PaymentTestSuiteNoDB { + @Test(groups = "fast") + public void testPaymentMethodExternalKeySetByPluginIfNonSpecified() throws Exception { + final Account account = Mockito.mock(Account.class); + final PaymentMethodPlugin paymentMethodPlugin = Mockito.mock(PaymentMethodPlugin.class); + final Iterable properties = ImmutableList.of(); + + final String paymentMethodExternalKey = UUID.randomUUID().toString(); + final UUID paymentMethodId1 = paymentMethodProcessor.addPaymentMethod(paymentMethodExternalKey, "__EXTERNAL_PAYMENT__", account, false, paymentMethodPlugin, properties, callContext, internalCallContext); + final PaymentMethod paymentMethod1 = paymentMethodProcessor.getPaymentMethodById(paymentMethodId1, false, false, properties, callContext, internalCallContext); + Assert.assertEquals(paymentMethod1.getExternalKey(), paymentMethodExternalKey); + + // By default, the external payment plugin sets the external payment method id to "unknown" + final UUID paymentMethodId2 = paymentMethodProcessor.addPaymentMethod(null, "__EXTERNAL_PAYMENT__", account, false, paymentMethodPlugin, properties, callContext, internalCallContext); + final PaymentMethod paymentMethod2 = paymentMethodProcessor.getPaymentMethodById(paymentMethodId2, false, false, properties, callContext, internalCallContext); + Assert.assertEquals(paymentMethod2.getExternalKey(), "unknown"); + + // Make sure we don't try to set the same external key if the plugin returns duplicate external ids + final UUID paymentMethodId3 = paymentMethodProcessor.addPaymentMethod(null, "__EXTERNAL_PAYMENT__", account, false, paymentMethodPlugin, properties, callContext, internalCallContext); + final PaymentMethod paymentMethod3 = paymentMethodProcessor.getPaymentMethodById(paymentMethodId3, false, false, properties, callContext, internalCallContext); + Assert.assertNotEquals(paymentMethod3.getExternalKey(), "unknown"); + } + @Test(groups = "fast") public void testGetExternalPaymentProviderPlugin() throws Exception { final Iterable properties = ImmutableList.of(); From 75e1d016367351d13fc0794e4612c4135cd060f8 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 23 Sep 2015 20:45:40 -0700 Subject: [PATCH 100/137] jaxrs: Add new threadpool to allow some apis to run independant tasks in parallel. Implement parallel query param for the account timeline --- .../killbill/billing/jaxrs/JaxrsService.java | 23 +++ .../billing/jaxrs/DefaultJaxrsService.java | 61 ++++++++ .../billing/jaxrs/JaxrsExecutors.java | 82 ++++++++++ .../jaxrs/glue/DefaultJaxrsModule.java | 43 ++++++ .../jaxrs/resources/AccountResource.java | 140 ++++++++++++++++-- .../jaxrs/resources/JaxrsResource.java | 2 + .../listeners/KillbillGuiceListener.java | 1 + .../server/modules/KillbillServerModule.java | 2 + .../billing/util/config/JaxrsConfig.java | 37 +++++ 9 files changed, 378 insertions(+), 13 deletions(-) create mode 100644 api/src/main/java/org/killbill/billing/jaxrs/JaxrsService.java create mode 100644 jaxrs/src/main/java/org/killbill/billing/jaxrs/DefaultJaxrsService.java create mode 100644 jaxrs/src/main/java/org/killbill/billing/jaxrs/JaxrsExecutors.java create mode 100644 jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java create mode 100644 util/src/main/java/org/killbill/billing/util/config/JaxrsConfig.java diff --git a/api/src/main/java/org/killbill/billing/jaxrs/JaxrsService.java b/api/src/main/java/org/killbill/billing/jaxrs/JaxrsService.java new file mode 100644 index 0000000000..6d4ae5580c --- /dev/null +++ b/api/src/main/java/org/killbill/billing/jaxrs/JaxrsService.java @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs; + +import org.killbill.billing.platform.api.KillbillService; + +public interface JaxrsService extends KillbillService { +} diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/DefaultJaxrsService.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/DefaultJaxrsService.java new file mode 100644 index 0000000000..bbd4d7bc95 --- /dev/null +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/DefaultJaxrsService.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs; + +import javax.inject.Inject; + +import org.killbill.billing.platform.api.LifecycleHandlerType; +import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel; +import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue; +import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultJaxrsService implements JaxrsService { + + private static final Logger log = LoggerFactory.getLogger(DefaultJaxrsService.class); + + private static final String JAXRS_SERVICE_NAME = "jaxrs-service"; + + private final JaxrsExecutors jaxrsExecutors; + + @Inject + public DefaultJaxrsService(final JaxrsExecutors jaxrsExecutors) { + this.jaxrsExecutors = jaxrsExecutors; + } + + @Override + public String getName() { + return JAXRS_SERVICE_NAME; + } + + @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE) + public void initialize() throws NotificationQueueAlreadyExists { + jaxrsExecutors.initialize(); + } + + @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE) + public void stop() throws NoSuchNotificationQueue { + try { + jaxrsExecutors.stop(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("JaxrsService got interrupted", e); + } + } +} diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/JaxrsExecutors.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/JaxrsExecutors.java new file mode 100644 index 0000000000..b2625f343d --- /dev/null +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/JaxrsExecutors.java @@ -0,0 +1,82 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; + +import org.killbill.billing.util.config.JaxrsConfig; +import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor; + +public class JaxrsExecutors { + + + private static final long TIMEOUT_EXECUTOR_SEC = 3L; + + private static final String JAXRS_THREAD_PREFIX = "jaxrs-th-"; + private static final String JAXRS_TH_GROUP_NAME = "jaxrs-grp"; + + + private final JaxrsConfig JaxrsConfig; + + private volatile ExecutorService jaxrsExecutorService; + + @Inject + public JaxrsExecutors(JaxrsConfig JaxrsConfig) { + this.JaxrsConfig = JaxrsConfig; + + } + + public void initialize() { + this.jaxrsExecutorService = createJaxrsExecutorService(); + } + + + public void stop() throws InterruptedException { + jaxrsExecutorService.shutdownNow(); + jaxrsExecutorService.awaitTermination(TIMEOUT_EXECUTOR_SEC, TimeUnit.SECONDS); + jaxrsExecutorService = null; + + } + + public ExecutorService getJaxrsExecutorService() { + return jaxrsExecutorService; + } + + private ExecutorService createJaxrsExecutorService() { + return new WithProfilingThreadPoolExecutor(JaxrsConfig.getJaxrsThreadNb(), + JaxrsConfig.getJaxrsThreadNb(), + 0L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(), + new ThreadFactory() { + + @Override + public Thread newThread(final Runnable r) { + final Thread th = new Thread(new ThreadGroup(JAXRS_TH_GROUP_NAME), r); + th.setName(JAXRS_THREAD_PREFIX + th.getId()); + return th; + } + }); + + } +} diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java new file mode 100644 index 0000000000..f19b31dbb7 --- /dev/null +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.jaxrs.glue; + +import org.killbill.billing.jaxrs.DefaultJaxrsService; +import org.killbill.billing.jaxrs.JaxrsExecutors; +import org.killbill.billing.jaxrs.JaxrsService; +import org.killbill.billing.platform.api.KillbillConfigSource; +import org.killbill.billing.util.config.JaxrsConfig; +import org.killbill.billing.util.glue.KillBillModule; +import org.skife.config.ConfigurationObjectFactory; + +public class DefaultJaxrsModule extends KillBillModule { + + public DefaultJaxrsModule(final KillbillConfigSource configSource) { + super(configSource); + } + + @Override + protected void configure() { + final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(skifeConfigSource); + final JaxrsConfig jaxrsConfig = factory.build(JaxrsConfig.class); + bind(JaxrsConfig.class).toInstance(jaxrsConfig); + + bind(JaxrsExecutors.class).asEagerSingleton(); + bind(JaxrsService.class).to(DefaultJaxrsService.class).asEagerSingleton(); + } +} diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java index 374fb62611..66af04cfd0 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java @@ -26,6 +26,12 @@ import java.util.LinkedList; import java.util.List; import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; @@ -60,6 +66,7 @@ import org.killbill.billing.invoice.api.InvoicePayment; import org.killbill.billing.invoice.api.InvoicePaymentApi; import org.killbill.billing.invoice.api.InvoiceUserApi; +import org.killbill.billing.jaxrs.JaxrsExecutors; import org.killbill.billing.jaxrs.json.AccountEmailJson; import org.killbill.billing.jaxrs.json.AccountJson; import org.killbill.billing.jaxrs.json.AccountTimelineJson; @@ -97,6 +104,7 @@ import org.killbill.billing.util.audit.AccountAuditLogs; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.TenantContext; +import org.killbill.billing.util.config.JaxrsConfig; import org.killbill.billing.util.config.PaymentConfig; import org.killbill.billing.util.entity.Pagination; import org.killbill.billing.util.tag.ControlTagType; @@ -131,6 +139,8 @@ public class AccountResource extends JaxRsResourceBase { private final InvoicePaymentApi invoicePaymentApi; private final OverdueInternalApi overdueApi; private final PaymentConfig paymentConfig; + private final JaxrsExecutors jaxrsExecutors; + private final JaxrsConfig jaxrsConfig; @Inject public AccountResource(final JaxrsUriBuilder uriBuilder, @@ -145,6 +155,8 @@ public AccountResource(final JaxrsUriBuilder uriBuilder, final OverdueInternalApi overdueApi, final Clock clock, final PaymentConfig paymentConfig, + final JaxrsExecutors jaxrsExecutors, + final JaxrsConfig jaxrsConfig, final Context context) { super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountApi, paymentApi, clock, context); this.subscriptionApi = subscriptionApi; @@ -152,6 +164,8 @@ public AccountResource(final JaxrsUriBuilder uriBuilder, this.invoicePaymentApi = invoicePaymentApi; this.overdueApi = overdueApi; this.paymentConfig = paymentConfig; + this.jaxrsExecutors = jaxrsExecutors; + this.jaxrsConfig = jaxrsConfig; } @Timed @@ -353,6 +367,7 @@ public Response cancelAccount(@PathParam("accountId") final String accountId, return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } + @Timed @GET @Path("/{accountId:" + UUID_PATTERN + "}/" + TIMELINE) @@ -362,28 +377,127 @@ public Response cancelAccount(@PathParam("accountId") final String accountId, @ApiResponse(code = 404, message = "Account not found")}) public Response getAccountTimeline(@PathParam("accountId") final String accountIdString, @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode, - @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException, SubscriptionApiException { + @QueryParam(QUERY_PARALLEL) @DefaultValue("false") final Boolean parallel, + @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException, SubscriptionApiException, InvoiceApiException { final TenantContext tenantContext = context.createContext(request); final UUID accountId = UUID.fromString(accountIdString); final Account account = accountUserApi.getAccountById(accountId, tenantContext); - // Get the invoices - final List invoices = invoiceApi.getInvoicesByAccount(account.getId(), tenantContext); + final Callable> bundlesCallable = new Callable>() { + @Override + public List call() throws Exception { + return subscriptionApi.getSubscriptionBundlesForAccountId(account.getId(), tenantContext); + } + }; + final Callable> invoicesCallable = new Callable>() { + @Override + public List call() throws Exception { + return invoiceApi.getInvoicesByAccount(account.getId(), tenantContext); + } + }; + final Callable> invoicePaymentsCallable = new Callable>() { + @Override + public List call() throws Exception { + return invoicePaymentApi.getInvoicePaymentsByAccount(accountId, tenantContext); + } + }; + final Callable> paymentsCallable = new Callable>() { + @Override + public List call() throws Exception { + return paymentApi.getAccountPayments(accountId, false, ImmutableList.of(), tenantContext); + } + }; + final Callable auditsCallable = new Callable() { + @Override + public AccountAuditLogs call() throws Exception { + return auditUserApi.getAccountAuditLogs(accountId, auditMode.getLevel(), tenantContext); + } + }; + + final AccountTimelineJson json; + + List invoices = null; + List bundles = null; + List invoicePayments = null; + List payments = null; + AccountAuditLogs accountAuditLogs = null; + + if (parallel) { + + final ExecutorService executor = jaxrsExecutors.getJaxrsExecutorService(); + final Future> futureBundlesCallable = executor.submit(bundlesCallable); + final Future> futureInvoicesCallable = executor.submit(invoicesCallable); + final Future> futureInvoicePaymentsCallable = executor.submit(invoicePaymentsCallable); + final Future> futurePaymentsCallable = executor.submit(paymentsCallable); + final Future futureAuditsCallable = executor.submit(auditsCallable); + + try { + long ini = System.currentTimeMillis(); + do { + bundles = (bundles == null) ? runCallableAndHandleTimeout(futureBundlesCallable, 100) : bundles; + invoices = (invoices == null) ? runCallableAndHandleTimeout(futureInvoicesCallable, 100) : invoices; + invoicePayments = (invoicePayments == null) ? runCallableAndHandleTimeout(futureInvoicePaymentsCallable, 100) : invoicePayments; + payments = (payments == null) ? runCallableAndHandleTimeout(futurePaymentsCallable, 100) : payments; + accountAuditLogs = (accountAuditLogs == null) ? runCallableAndHandleTimeout(futureAuditsCallable, 100) : accountAuditLogs; + } while ((System.currentTimeMillis() - ini < jaxrsConfig.getJaxrsTimeout().getMillis()) && + (bundles == null || invoices == null || invoicePayments == null || payments == null || accountAuditLogs == null)); + + if (bundles == null || invoices == null || invoicePayments == null || payments == null || accountAuditLogs == null) { + Response.status(Status.SERVICE_UNAVAILABLE).build(); + } + } catch (InterruptedException e) { + handleCallableException(e, ImmutableList.of(futureBundlesCallable, futureInvoicesCallable, futureInvoicePaymentsCallable, futurePaymentsCallable, futureAuditsCallable)); + } catch (ExecutionException e) { + handleCallableException(e.getCause(), ImmutableList.of(futureBundlesCallable, futureInvoicesCallable, futureInvoicePaymentsCallable, futurePaymentsCallable, futureAuditsCallable)); + } - // Get the payments - final List payments = paymentApi.getAccountPayments(accountId, false, ImmutableList.of(), tenantContext); + } else { + try { + invoices = invoicesCallable.call(); + payments = paymentsCallable.call(); + bundles = bundlesCallable.call(); + accountAuditLogs = auditsCallable.call(); + invoicePayments = invoicePaymentsCallable.call(); + } catch (Exception e) { + handleCallableException(e); + } + } - // Get the bundles - final List bundles = subscriptionApi.getSubscriptionBundlesForAccountId(account.getId(), tenantContext); + json = new AccountTimelineJson(account, invoices, payments, invoicePayments, bundles, accountAuditLogs); + return Response.status(Status.OK).entity(json).build(); + } - // Get all audit logs - final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(accountId, auditMode.getLevel(), tenantContext); + private T runCallableAndHandleTimeout(final Future future, final long timeoutMsec) throws ExecutionException, InterruptedException { + try { + return future.get(timeoutMsec, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + return null; + } + } - final List invoicePayments = invoicePaymentApi.getInvoicePaymentsByAccount(accountId, tenantContext); - final AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, invoicePayments, bundles, - accountAuditLogs); - return Response.status(Status.OK).entity(json).build(); + private void handleCallableException(final Throwable causeOrException, final List toBeCancelled) throws AccountApiException, SubscriptionApiException, PaymentApiException, InvoiceApiException { + for (final Future f : toBeCancelled) { + f.cancel(true); + } + handleCallableException(causeOrException); + } + + private void handleCallableException(final Throwable causeOrException) throws AccountApiException, SubscriptionApiException, PaymentApiException, InvoiceApiException { + if (causeOrException instanceof AccountApiException) { + throw (AccountApiException) causeOrException; + } else if (causeOrException instanceof SubscriptionApiException) { + throw (SubscriptionApiException) causeOrException; + } else if (causeOrException instanceof InvoiceApiException) { + throw (InvoiceApiException) causeOrException; + } else if (causeOrException instanceof PaymentApiException) { + throw (PaymentApiException) causeOrException; + } else { + if (causeOrException instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + throw new RuntimeException(causeOrException.getMessage(), causeOrException); + } } /* diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java index 076fd5503f..7a97f65dd5 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java @@ -116,6 +116,8 @@ public interface JaxrsResource { public static final String QUERY_AUDIT = "audit"; + public static final String QUERY_PARALLEL = "parallel"; + public static final String QUERY_NOTIFICATION_CALLBACK = "cb"; public static final String PAGINATION = "pagination"; diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java index 9f76e4f1b3..8e37e121f4 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java @@ -76,6 +76,7 @@ protected ServletModule getServletModule() { // In order to use the GZIPContentEncodingFilter, the jersey param "com.sun.jersey.config.feature.logging.DisableEntitylogging" // must not be set to false. + if (Boolean.valueOf(System.getProperty(ENABLE_HTTP_GZIP_RESPONSES, "false"))) { logger.info("Enable http gzip responses"); builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName()); diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java index a67a7d5f85..7322ef04f1 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java @@ -26,6 +26,7 @@ import org.killbill.billing.currency.glue.CurrencyModule; import org.killbill.billing.entitlement.glue.DefaultEntitlementModule; import org.killbill.billing.invoice.glue.DefaultInvoiceModule; +import org.killbill.billing.jaxrs.glue.DefaultJaxrsModule; import org.killbill.billing.jaxrs.resources.AccountResource; import org.killbill.billing.jaxrs.resources.AdminResource; import org.killbill.billing.jaxrs.resources.BundleResource; @@ -161,6 +162,7 @@ protected void installKillbillModules() { install(new TemplateModule(configSource)); install(new DefaultTenantModule(configSource)); install(new UsageModule(configSource)); + install(new DefaultJaxrsModule(configSource)); } protected void configureResources() { diff --git a/util/src/main/java/org/killbill/billing/util/config/JaxrsConfig.java b/util/src/main/java/org/killbill/billing/util/config/JaxrsConfig.java new file mode 100644 index 0000000000..a8583e8546 --- /dev/null +++ b/util/src/main/java/org/killbill/billing/util/config/JaxrsConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.util.config; + +import org.skife.config.Config; +import org.skife.config.Default; +import org.skife.config.Description; +import org.skife.config.TimeSpan; + +public interface JaxrsConfig extends KillbillConfig { + + @Config("org.killbill.jaxrs.threads.pool.nb") + @Default("30") + @Description("Number of threads for jaxrs executor") + int getJaxrsThreadNb(); + + @Config("org.killbill.jaxrs.timeout") + @Default("30s") + @Description("Total timeout for all callables associated to a given api call (parallel mode)") + TimeSpan getJaxrsTimeout(); + +} From ce36048b30a7b89d8a369fadf7d685a7444d4724 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 29 Sep 2015 15:52:51 -0700 Subject: [PATCH 101/137] Increment oss pom version to use latest commons (and switch the payment executor to a DynamicThreadPoolExecutorWithLoggingOnExceptions kind) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 572a477da2..553510eae7 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.44 + 0.45 killbill 0.15.6-SNAPSHOT From 5bec17deadf78b5a08edac408b1dcc7bb765b50d Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 30 Sep 2015 18:53:19 -0700 Subject: [PATCH 102/137] Introduce new internal account api to optimize code path and avoid fethcing account data from DB when the data is immutable. This change does not yet introduce the caching layer that is reuiqred to fully take advantage of that change. --- .../billing/account/api/DefaultAccount.java | 21 ++++--- .../api/DefaultImmutableAccountData.java | 62 +++++++++++++++++++ .../api/DefaultMutableAccountData.java | 0 .../api/svcs/DefaultAccountInternalApi.java | 42 ++++++++++--- .../account/api/AccountInternalApi.java | 26 +++++--- .../entitlement/AccountEntitlements.java | 4 +- .../entitlement/AccountEventsStreams.java | 4 +- .../billing/junction/BillingEvent.java | 4 -- .../billing/overdue/OverdueInternalApi.java | 10 +-- .../api/DefaultEntitlementApi.java | 6 +- .../api/EntitlementDateHelper.java | 6 +- .../api/svcs/DefaultAccountEntitlements.java | 4 +- .../api/svcs/DefaultAccountEventsStreams.java | 10 +-- .../dao/OptimizedProxyBlockingStateDao.java | 27 ++++---- .../engine/core/DefaultEventsStream.java | 6 +- .../engine/core/EventsStreamBuilder.java | 16 ++--- .../api/TestEntitlementDateHelper.java | 1 + .../billing/invoice/InvoiceDispatcher.java | 44 +++++++------ .../migration/DefaultInvoiceMigrationApi.java | 7 +-- .../api/user/DefaultInvoiceUserApi.java | 5 +- .../generator/DefaultInvoiceGenerator.java | 7 +-- ...FixedAndRecurringInvoiceItemGenerator.java | 23 ++++--- .../invoice/generator/InvoiceGenerator.java | 5 +- .../generator/InvoiceItemGenerator.java | 4 +- .../generator/UsageInvoiceItemGenerator.java | 10 +-- .../ContiguousIntervalConsumableInArrear.java | 11 ++-- .../usage/SubscriptionConsumableInArrear.java | 6 +- .../billing/invoice/TestInvoiceHelper.java | 5 -- .../billing/invoice/dao/TestInvoiceDao.java | 2 + .../TestDefaultInvoiceGenerator.java | 1 + .../TestSubscriptionConsumableInArrear.java | 2 +- .../invoice/usage/TestUsageInArrearBase.java | 3 +- .../jaxrs/resources/AccountResource.java | 3 + .../billing/BillCycleDayCalculator.java | 33 +++++----- .../plumbing/billing/BlockingCalculator.java | 12 ++-- .../plumbing/billing/DefaultBillingEvent.java | 22 ++----- .../billing/DefaultInternalBillingApi.java | 33 +++++----- .../billing/TestBillCycleDayCalculator.java | 10 +-- .../plumbing/billing/TestBillingApi.java | 2 + .../billing/TestBlockingCalculator.java | 22 +++---- .../billing/TestDefaultBillingEvent.java | 4 +- .../api/DefaultOverdueInternalApi.java | 12 ++-- .../applicator/OverdueStateApplicator.java | 28 +++++---- .../calculator/BillingStateCalculator.java | 14 ++--- .../overdue/wrapper/OverdueWrapper.java | 7 ++- .../wrapper/OverdueWrapperFactory.java | 9 ++- .../billing/overdue/TestOverdueHelper.java | 8 +-- .../TestOverdueStateApplicator.java | 4 +- .../TestBillingStateCalculator.java | 3 +- .../overdue/wrapper/TestOverdueWrapper.java | 14 ++--- .../core/janitor/CompletionTaskBase.java | 6 +- .../InvoicePaymentControlPluginApi.java | 8 +-- .../payment/invoice/PaymentTagHandler.java | 15 ++--- pom.xml | 2 +- 54 files changed, 358 insertions(+), 297 deletions(-) create mode 100644 account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java rename {api => account}/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java (100%) diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java b/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java index 25384b3643..957b2e2959 100644 --- a/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java +++ b/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java @@ -295,15 +295,18 @@ public Account mergeWithDelegate(final Account currentAccount) { accountData.setCurrency(currentAccount.getCurrency()); } - if (billCycleDayLocal != null && billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL && currentAccount.getBillCycleDayLocal() != null && currentAccount.getBillCycleDayLocal() != DEFAULT_BILLING_CYCLE_DAY_LOCAL && !billCycleDayLocal.equals(currentAccount.getBillCycleDayLocal())) { - throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account BCD yet: new=%s, current=%s", - billCycleDayLocal, currentAccount.getBillCycleDayLocal())); - } else if (billCycleDayLocal != null && billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL) { - // Junction sets it + + if (currentAccount.getBillCycleDayLocal() != DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is already a BCD set + billCycleDayLocal != null && // and the proposed date is not null + billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL && // and the proposed date is not 0 + !currentAccount.getBillCycleDayLocal().equals(billCycleDayLocal)) { // and it does not match we we have + throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account BCD yet: new=%s, current=%s", billCycleDayLocal, currentAccount.getBillCycleDayLocal())); + } else if (currentAccount.getBillCycleDayLocal() == DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is *not* already a BCD set + billCycleDayLocal != null && // and the value proposed is not null + billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL) { // and the proposed date is not 0 accountData.setBillCycleDayLocal(billCycleDayLocal); } else { - // Default to current value - accountData.setBillCycleDayLocal(currentAccount.getBillCycleDayLocal() == null ? DEFAULT_BILLING_CYCLE_DAY_LOCAL : currentAccount.getBillCycleDayLocal()); + accountData.setBillCycleDayLocal(currentAccount.getBillCycleDayLocal()); } // Set all updatable fields with the new values if non null, otherwise defaults to the current values @@ -336,6 +339,10 @@ public Account mergeWithDelegate(final Account currentAccount) { return new DefaultAccount(currentAccount.getId(), accountData); } + public ImmutableAccountData toImmutableAccountData() { + return new DefaultImmutableAccountData(this); + } + @Override public String toString() { return "DefaultAccount [externalKey=" + externalKey + diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java new file mode 100644 index 0000000000..e1df215c00 --- /dev/null +++ b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.account.api; + +import java.util.UUID; + +import org.joda.time.DateTimeZone; +import org.killbill.billing.catalog.api.Currency; + +public class DefaultImmutableAccountData implements ImmutableAccountData { + + private final UUID id; + private final String externalKey; + private final Currency currency; + private final DateTimeZone dateTimeZone; + + public DefaultImmutableAccountData(final UUID id, final String externalKey, final Currency currency, final DateTimeZone dateTimeZone) { + this.id = id; + this.externalKey = externalKey; + this.currency = currency; + this.dateTimeZone = dateTimeZone; + } + + public DefaultImmutableAccountData(final Account account) { + this(account.getId(), account.getExternalKey(), account.getCurrency(), account.getTimeZone()); + } + + @Override + public UUID getId() { + return id; + } + + @Override + public String getExternalKey() { + return externalKey; + } + + @Override + public Currency getCurrency() { + return currency; + } + + @Override + public DateTimeZone getTimeZone() { + return dateTimeZone; + } +} diff --git a/api/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java b/account/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java similarity index 100% rename from api/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java rename to account/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java index 5502b4e93b..3b918f674a 100644 --- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java +++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java @@ -24,23 +24,22 @@ import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; -import org.killbill.billing.account.api.AccountData; import org.killbill.billing.account.api.AccountEmail; import org.killbill.billing.account.api.AccountInternalApi; import org.killbill.billing.account.api.DefaultAccount; import org.killbill.billing.account.api.DefaultAccountEmail; +import org.killbill.billing.account.api.DefaultImmutableAccountData; +import org.killbill.billing.account.api.ImmutableAccountData; +import org.killbill.billing.account.api.MutableAccountData; import org.killbill.billing.account.dao.AccountDao; import org.killbill.billing.account.dao.AccountEmailModelDao; import org.killbill.billing.account.dao.AccountModelDao; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; -import org.killbill.billing.util.entity.DefaultPagination; -import org.killbill.billing.util.entity.Pagination; import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterators; public class DefaultAccountInternalApi implements AccountInternalApi { @@ -67,20 +66,25 @@ public Account getAccountByRecordId(final Long recordId, final InternalTenantCon } @Override - public void updateAccount(final String externalKey, final AccountData accountData, + public void updateBCD(final String externalKey, final int bcd, final InternalCallContext context) throws AccountApiException { final Account currentAccount = getAccountByKey(externalKey, context); if (currentAccount == null) { throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey); } - // Set unspecified (null) fields to their current values - final Account updatedAccount = new DefaultAccount(currentAccount.getId(), accountData); - final AccountModelDao accountToUpdate = new AccountModelDao(currentAccount.getId(), updatedAccount.mergeWithDelegate(currentAccount)); - + final MutableAccountData mutableAccountData = currentAccount.toMutableAccountData(); + mutableAccountData.setBillCycleDayLocal(bcd); + final AccountModelDao accountToUpdate = new AccountModelDao(currentAccount.getId(), mutableAccountData); accountDao.update(accountToUpdate, context); } + @Override + public int getBCD(final UUID accountId, final InternalTenantContext context) throws AccountApiException { + final Account account = getAccountById(accountId, context); + return account.getBillCycleDayLocal(); + } + @Override public List getEmails(final UUID accountId, final InternalTenantContext context) { @@ -119,6 +123,24 @@ public UUID getByRecordId(final Long recordId, final InternalTenantContext conte return accountModelDao.getId(); } + @Override + public ImmutableAccountData getImmutableAccountDataById(final UUID accountId, final InternalTenantContext context) throws AccountApiException { + final Account account = getAccountById(accountId, context); + return new DefaultImmutableAccountData(account); + } + + @Override + public ImmutableAccountData getImmutableAccountDataByKey(final String key, final InternalTenantContext context) throws AccountApiException { + final Account account = getAccountByKey(key, context); + return new DefaultImmutableAccountData(account); + } + + @Override + public ImmutableAccountData getImmutableAccountDataByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException { + final Account account = getAccountByRecordId(recordId, context); + return new DefaultImmutableAccountData(account); + } + private AccountModelDao getAccountModelDaoByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException { final AccountModelDao accountModelDao = accountDao.getByRecordId(recordId, context); if (accountModelDao == null) { @@ -126,4 +148,6 @@ private AccountModelDao getAccountModelDaoByRecordId(final Long recordId, final } return accountModelDao; } + + } diff --git a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java index 8e6ab3c472..9a61b19e14 100644 --- a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java +++ b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java @@ -21,23 +21,31 @@ import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; -import org.killbill.billing.util.entity.Pagination; public interface AccountInternalApi { - public Account getAccountByKey(String key, InternalTenantContext context) throws AccountApiException; + Account getAccountByKey(String key, InternalTenantContext context) throws AccountApiException; - public Account getAccountById(UUID accountId, InternalTenantContext context) throws AccountApiException; + Account getAccountById(UUID accountId, InternalTenantContext context) throws AccountApiException; - public Account getAccountByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException; + Account getAccountByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException; - public void updateAccount(String key, AccountData accountData, InternalCallContext context) throws AccountApiException; + void updateBCD(String key, int bcd, InternalCallContext context) throws AccountApiException; - public List getEmails(UUID accountId, InternalTenantContext context); + int getBCD(UUID accountId, InternalTenantContext context) throws AccountApiException; - public void removePaymentMethod(UUID accountId, InternalCallContext context) throws AccountApiException; + List getEmails(UUID accountId, InternalTenantContext context); - public void updatePaymentMethod(UUID accountId, UUID paymentMethodId, InternalCallContext context) throws AccountApiException; + void removePaymentMethod(UUID accountId, InternalCallContext context) throws AccountApiException; + + void updatePaymentMethod(UUID accountId, UUID paymentMethodId, InternalCallContext context) throws AccountApiException; + + UUID getByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException; + + ImmutableAccountData getImmutableAccountDataById(UUID accountId, InternalTenantContext context) throws AccountApiException; + + ImmutableAccountData getImmutableAccountDataByKey(String key, InternalTenantContext context) throws AccountApiException; + + ImmutableAccountData getImmutableAccountDataByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException; - public UUID getByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException; } diff --git a/api/src/main/java/org/killbill/billing/entitlement/AccountEntitlements.java b/api/src/main/java/org/killbill/billing/entitlement/AccountEntitlements.java index df3fd30787..113605816d 100644 --- a/api/src/main/java/org/killbill/billing/entitlement/AccountEntitlements.java +++ b/api/src/main/java/org/killbill/billing/entitlement/AccountEntitlements.java @@ -20,14 +20,14 @@ import java.util.Map; import java.util.UUID; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.entitlement.api.Entitlement; import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle; // Wrapper object to save on DAO calls public interface AccountEntitlements { - public Account getAccount(); + public ImmutableAccountData getAccount(); // Map bundle id -> bundle public Map getBundles(); diff --git a/api/src/main/java/org/killbill/billing/entitlement/AccountEventsStreams.java b/api/src/main/java/org/killbill/billing/entitlement/AccountEventsStreams.java index f7b558acea..bd674fb076 100644 --- a/api/src/main/java/org/killbill/billing/entitlement/AccountEventsStreams.java +++ b/api/src/main/java/org/killbill/billing/entitlement/AccountEventsStreams.java @@ -20,13 +20,13 @@ import java.util.Map; import java.util.UUID; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle; // Wrapper object to save on DAO calls public interface AccountEventsStreams { - public Account getAccount(); + public ImmutableAccountData getAccount(); // Map bundle id -> bundle public Map getBundles(); diff --git a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java index 02c15d08b9..a08e8c321e 100644 --- a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java +++ b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java @@ -34,10 +34,6 @@ public interface BillingEvent extends Comparable { - /** - * @return the account that this billing event is associated with - */ - Account getAccount(); /** * @return the billCycleDay in the account timezone as seen for that subscription at that time diff --git a/api/src/main/java/org/killbill/billing/overdue/OverdueInternalApi.java b/api/src/main/java/org/killbill/billing/overdue/OverdueInternalApi.java index afc01ca570..0b376b85fc 100644 --- a/api/src/main/java/org/killbill/billing/overdue/OverdueInternalApi.java +++ b/api/src/main/java/org/killbill/billing/overdue/OverdueInternalApi.java @@ -16,7 +16,7 @@ package org.killbill.billing.overdue; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.overdue.api.OverdueApiException; import org.killbill.billing.overdue.api.OverdueState; import org.killbill.billing.overdue.config.api.BillingState; @@ -26,12 +26,12 @@ public interface OverdueInternalApi { - public OverdueState refreshOverdueStateFor(Account overdueable, CallContext context) throws OverdueException, OverdueApiException; + public OverdueState refreshOverdueStateFor(ImmutableAccountData overdueable, CallContext context) throws OverdueException, OverdueApiException; - public void setOverrideBillingStateForAccount(Account overdueable, BillingState state, CallContext context) throws OverdueException; + public void setOverrideBillingStateForAccount(ImmutableAccountData overdueable, BillingState state, CallContext context) throws OverdueException; - public OverdueState getOverdueStateFor(Account overdueable, TenantContext context) throws OverdueException; + public OverdueState getOverdueStateFor(ImmutableAccountData overdueable, TenantContext context) throws OverdueException; - public BillingState getBillingStateFor(Account overdueable, TenantContext context) throws OverdueException; + public BillingState getBillingStateFor(ImmutableAccountData overdueable, TenantContext context) throws OverdueException; } diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java index 7f2ecc60c1..0157feffea 100644 --- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java +++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java @@ -26,9 +26,9 @@ import org.joda.time.LocalDate; import org.killbill.billing.ErrorCode; import org.killbill.billing.ObjectType; -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.BillingActionPolicy; @@ -303,7 +303,7 @@ public Void doCall(final EntitlementApi entitlementApi, final EntitlementContext } final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, contextWithValidAccountRecordId); - final Account account = accountApi.getAccountById(bundle.getAccountId(), contextWithValidAccountRecordId); + final ImmutableAccountData account = accountApi.getImmutableAccountDataById(bundle.getAccountId(), contextWithValidAccountRecordId); final SubscriptionBase baseSubscription = subscriptionBaseInternalApi.getBaseSubscription(bundleId, contextWithValidAccountRecordId); final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEffectiveDate(), baseSubscription.getStartDate(), contextWithValidAccountRecordId); @@ -358,7 +358,7 @@ public Void doCall(final EntitlementApi entitlementApi, final EntitlementContext try { final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context); final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, contextWithValidAccountRecordId); - final Account account = accountApi.getAccountById(bundle.getAccountId(), contextWithValidAccountRecordId); + final ImmutableAccountData account = accountApi.getImmutableAccountDataById(bundle.getAccountId(), contextWithValidAccountRecordId); final SubscriptionBase baseSubscription = subscriptionBaseInternalApi.getBaseSubscription(bundleId, contextWithValidAccountRecordId); final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEffectiveDate(), baseSubscription.getStartDate(), contextWithValidAccountRecordId); diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java index 3b0b63b3cc..7d6272c4b3 100644 --- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java +++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java @@ -18,12 +18,10 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.joda.time.Interval; import org.joda.time.LocalDate; - -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.clock.Clock; import org.killbill.clock.ClockUtil; @@ -40,7 +38,7 @@ public EntitlementDateHelper(final AccountInternalApi accountApi, final Clock cl public DateTime fromLocalDateAndReferenceTime(final LocalDate requestedDate, final DateTime referenceDateTime, final InternalTenantContext callContext) throws EntitlementApiException { try { - final Account account = accountApi.getAccountByRecordId(callContext.getAccountRecordId(), callContext); + final ImmutableAccountData account = accountApi.getImmutableAccountDataByRecordId(callContext.getAccountRecordId(), callContext); return ClockUtil.computeDateTimeWithUTCReferenceTime(requestedDate, referenceDateTime.toDateTime(DateTimeZone.UTC).toLocalTime(), account.getTimeZone(), clock); } catch (AccountApiException e) { throw new EntitlementApiException(e); diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultAccountEntitlements.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultAccountEntitlements.java index f86b5872ae..deca457f34 100644 --- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultAccountEntitlements.java +++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultAccountEntitlements.java @@ -20,7 +20,7 @@ import java.util.Map; import java.util.UUID; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.entitlement.AccountEntitlements; import org.killbill.billing.entitlement.AccountEventsStreams; import org.killbill.billing.entitlement.api.Entitlement; @@ -37,7 +37,7 @@ public DefaultAccountEntitlements(final AccountEventsStreams accountEventsStream } @Override - public Account getAccount() { + public ImmutableAccountData getAccount() { return accountEventsStreams.getAccount(); } diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java index df46e81f8b..d1b09a8455 100644 --- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java +++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java @@ -21,7 +21,7 @@ import java.util.Map; import java.util.UUID; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.entitlement.AccountEventsStreams; import org.killbill.billing.entitlement.EventsStream; import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle; @@ -31,11 +31,11 @@ public class DefaultAccountEventsStreams implements AccountEventsStreams { - private final Account account; + private final ImmutableAccountData account; private final Map> eventsStreams; private final Map bundles = new HashMap(); - public DefaultAccountEventsStreams(final Account account, + public DefaultAccountEventsStreams(final ImmutableAccountData account, final Iterable bundles, final Map> eventsStreams) { this.account = account; @@ -45,12 +45,12 @@ public DefaultAccountEventsStreams(final Account account, } } - public DefaultAccountEventsStreams(final Account account) { + public DefaultAccountEventsStreams(final ImmutableAccountData account) { this(account, ImmutableList.of(), ImmutableMap.>of()); } @Override - public Account getAccount() { + public ImmutableAccountData getAccount() { return account; } diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java index 1457e63419..cef565e031 100644 --- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java +++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java @@ -21,12 +21,9 @@ import javax.annotation.Nullable; -import org.skife.jdbi.v2.IDBI; - -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.ProductCategory; -import org.killbill.clock.Clock; import org.killbill.billing.entitlement.EventsStream; import org.killbill.billing.entitlement.api.BlockingState; import org.killbill.billing.entitlement.api.BlockingStateType; @@ -37,6 +34,8 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle; import org.killbill.billing.util.cache.CacheControllerDispatcher; import org.killbill.billing.util.dao.NonEntityDao; +import org.killbill.clock.Clock; +import org.skife.jdbi.v2.IDBI; import com.google.common.collect.ImmutableList; @@ -57,22 +56,20 @@ public OptimizedProxyBlockingStateDao(final EventsStreamBuilder eventsStreamBuil *

* This is a special method for EventsStreamBuilder to save some DAO calls. * - * @param subscriptionBlockingStatesOnDisk - * blocking states on disk for that subscription - * @param allBlockingStatesOnDiskForAccount - * all blocking states on disk for that account - * @param account account associated with the subscription - * @param bundle bundle associated with the subscription - * @param baseSubscription base subscription (ProductCategory.BASE) associated with that bundle - * @param subscription subscription for which to build blocking states - * @param allSubscriptionsForBundle all subscriptions associated with that bundle - * @param context call context + * @param subscriptionBlockingStatesOnDisk blocking states on disk for that subscription + * @param allBlockingStatesOnDiskForAccount all blocking states on disk for that account + * @param account account associated with the subscription + * @param bundle bundle associated with the subscription + * @param baseSubscription base subscription (ProductCategory.BASE) associated with that bundle + * @param subscription subscription for which to build blocking states + * @param allSubscriptionsForBundle all subscriptions associated with that bundle + * @param context call context * @return blocking states for that subscription * @throws EntitlementApiException */ public List getBlockingHistory(final List subscriptionBlockingStatesOnDisk, final List allBlockingStatesOnDiskForAccount, - final Account account, + final ImmutableAccountData account, final SubscriptionBaseBundle bundle, @Nullable final SubscriptionBase baseSubscription, final SubscriptionBase subscription, diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java index 192d301478..23795b6b5a 100644 --- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java +++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java @@ -27,7 +27,7 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.Plan; import org.killbill.billing.catalog.api.Product; @@ -55,7 +55,7 @@ public class DefaultEventsStream implements EventsStream { - private final Account account; + private final ImmutableAccountData account; private final SubscriptionBaseBundle bundle; // All blocking states for the account, associated bundle or subscription private final List blockingStates; @@ -80,7 +80,7 @@ public class DefaultEventsStream implements EventsStream { private BlockingState entitlementCancelEvent; private EntitlementState entitlementState; - public DefaultEventsStream(final Account account, final SubscriptionBaseBundle bundle, + public DefaultEventsStream(final ImmutableAccountData account, final SubscriptionBaseBundle bundle, final List blockingStates, final BlockingChecker blockingChecker, @Nullable final SubscriptionBase baseSubscription, final SubscriptionBase subscription, final List allSubscriptionsForBundle, final InternalTenantContext contextWithValidAccountRecordId, final DateTime utcNow) { diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java index 8bb42d5ac3..afd51c895c 100644 --- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java +++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java @@ -29,9 +29,9 @@ import javax.inject.Singleton; import org.killbill.billing.ObjectType; -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.ProductCategory; import org.killbill.billing.entitlement.AccountEventsStreams; @@ -123,9 +123,9 @@ public AccountEventsStreams buildForAccount(final InternalTenantContext internal // Special signature for ProxyBlockingStateDao to save a DAO call public AccountEventsStreams buildForAccount(final Map> subscriptions, final InternalTenantContext internalTenantContext) throws EntitlementApiException { // Retrieve the account - final Account account; + final ImmutableAccountData account; try { - account = accountInternalApi.getAccountByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext); + account = accountInternalApi.getImmutableAccountDataByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext); } catch (AccountApiException e) { throw new EntitlementApiException(e); } @@ -229,9 +229,9 @@ public EventsStream buildForEntitlement(final UUID entitlementId, final Internal throw new EntitlementApiException(e); } - final Account account; + final ImmutableAccountData account; try { - account = accountInternalApi.getAccountById(bundle.getAccountId(), internalTenantContext); + account = accountInternalApi.getImmutableAccountDataById(bundle.getAccountId(), internalTenantContext); } catch (AccountApiException e) { throw new EntitlementApiException(e); } @@ -244,7 +244,7 @@ public EventsStream buildForEntitlement(final UUID entitlementId, final Internal // Special signature for OptimizedProxyBlockingStateDao to save some DAO calls public EventsStream buildForEntitlement(final List blockingStatesForAccount, - final Account account, + final ImmutableAccountData account, final SubscriptionBaseBundle bundle, final SubscriptionBase baseSubscription, final List allSubscriptionsForBundle, @@ -253,7 +253,7 @@ public EventsStream buildForEntitlement(final List blockingStates } private EventsStream buildForEntitlement(final List blockingStatesForAccount, - final Account account, + final ImmutableAccountData account, final SubscriptionBaseBundle bundle, @Nullable final SubscriptionBase baseSubscription, final SubscriptionBase subscription, @@ -312,7 +312,7 @@ private EventsStream buildForEntitlement(final List blockingState return buildForEntitlement(account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, blockingStates, internalTenantContext); } - private EventsStream buildForEntitlement(final Account account, + private EventsStream buildForEntitlement(final ImmutableAccountData account, final SubscriptionBaseBundle bundle, @Nullable final SubscriptionBase baseSubscription, final SubscriptionBase subscription, diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java index 9a586a1914..ae889c93f8 100644 --- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java +++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java @@ -42,6 +42,7 @@ public void beforeMethod() throws Exception { account = Mockito.mock(Account.class); Mockito.when(accountInternalApi.getAccountByRecordId(Mockito.anyLong(), Mockito.any())).thenReturn(account); + Mockito.when(accountInternalApi.getImmutableAccountDataByRecordId(Mockito.anyLong(), Mockito.any())).thenReturn(account); dateHelper = new EntitlementDateHelper(accountInternalApi, clock); clock.resetDeltaFromReality();; } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java index 44606326b9..aa5198e2f2 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java @@ -36,6 +36,7 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.BillingActionPolicy; @@ -112,7 +113,6 @@ public class InvoiceDispatcher { private static final Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class); - private static final Ordering UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural(); private final static Joiner JOINER_COMMA = Joiner.on(","); private static final NullDryRunArguments NULL_DRY_RUN_ARGUMENTS = new NullDryRunArguments(); @@ -201,7 +201,7 @@ private Invoice processSubscriptionInternal(final UUID subscriptionId, final Dat } } - public Invoice processAccount(final UUID accountId, @Nullable final DateTime targetDate, + public Invoice processAccount(final UUID accountId, @Nullable final DateTime targetDate, @Nullable final DryRunArguments dryRunArguments, final InternalCallContext context) throws InvoiceApiException { GlobalLock lock = null; try { @@ -249,7 +249,8 @@ private Invoice processAccountWithLock(final UUID accountId, @Nullable final Dat private Invoice processAccountWithLockAndInputTargetDate(final UUID accountId, final DateTime targetDateTime, final BillingEventSet billingEvents, final boolean isDryRun, final InternalCallContext context) throws InvoiceApiException { try { - final Account account = accountApi.getAccountById(accountId, context); + final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context); + final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(billingEvents.iterator().next().getEffectiveDate(), account.getTimeZone(), clock); final List invoices = billingEvents.isAccountAutoInvoiceOff() ? @@ -300,13 +301,11 @@ public Invoice apply(final InvoiceModelDao input) { final CallContext callContext = buildCallContext(context); invoice.addInvoiceItems(invoicePluginDispatcher.getAdditionalInvoiceItems(invoice, callContext)); - - if (!isDryRun) { // Compute whether this is a new invoice object (or just some adjustments on an existing invoice), and extract invoiceIds for later use final Set uniqueInvoiceIds = getUniqueInvoiceIds(invoice); - final boolean isRealInvoiceWithItems = uniqueInvoiceIds.remove(invoice.getId()); + final boolean isRealInvoiceWithItems = uniqueInvoiceIds.remove(invoice.getId()); final Set adjustedUniqueOtherInvoiceId = uniqueInvoiceIds; logInvoiceWithItems(account, invoice, targetDate, adjustedUniqueOtherInvoiceId, isRealInvoiceWithItems); @@ -320,7 +319,6 @@ public Invoice apply(final InvoiceModelDao input) { final boolean isRealInvoiceWithNonEmptyItems = isThereAnyItemsLeft ? isRealInvoiceWithItems : false; - setChargedThroughDates(dateAndTimeZoneContext, invoice.getInvoiceItems(FixedPriceInvoiceItem.class), invoice.getInvoiceItems(RecurringInvoiceItem.class), context); // TODO we should send bus events when we commit the ionvoice on disk in commitInvoice @@ -338,7 +336,6 @@ public Invoice apply(final InvoiceModelDao input) { } } - private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final DateAndTimeZoneContext dateAndTimeZoneContext, final InternalCallContext context) { final Map> result = new HashMap>(); @@ -384,12 +381,12 @@ private FutureAccountNotifications createNextFutureNotificationDate(final Invoic private Iterable transformToInvoiceModelDao(final List invoiceItems) { return Iterables.transform(invoiceItems, - new Function() { - @Override - public InvoiceItemModelDao apply(final InvoiceItem input) { - return new InvoiceItemModelDao(input); - } - }); + new Function() { + @Override + public InvoiceItemModelDao apply(final InvoiceItem input) { + return new InvoiceItemModelDao(input); + } + }); } private Set getUniqueInvoiceIds(final Invoice invoice) { @@ -404,7 +401,7 @@ public UUID apply(@Nullable final InvoiceItem input) { return uniqueInvoiceIds; } - private void logInvoiceWithItems(final Account account, final Invoice invoice, final LocalDate targetDate, final Set adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithItems) { + private void logInvoiceWithItems(final ImmutableAccountData account, final Invoice invoice, final LocalDate targetDate, final Set adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithItems) { final StringBuilder tmp = new StringBuilder(); if (isRealInvoiceWithItems) { tmp.append(String.format("Generated invoice %s with %d items for accountId %s and targetDate %s:\n", invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate)); @@ -419,8 +416,7 @@ private void logInvoiceWithItems(final Account account, final Invoice invoice, f log.info(tmp.toString()); } - - private boolean commitInvoiceAndSetFutureNotifications(final Account account, final InvoiceModelDao invoiceModelDao, + private boolean commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account, final InvoiceModelDao invoiceModelDao, final Iterable invoiceItemModelDaos, final FutureAccountNotifications futureAccountNotifications, boolean isRealInvoiceWithItems, final InternalCallContext context) throws SubscriptionBaseApiException, InvoiceApiException { @@ -442,7 +438,7 @@ public boolean apply(@Nullable final InvoiceItemModelDao input) { return isThereAnyItemsLeft; } - private void postEvents(final Account account, final Invoice invoice, final Set adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithNonEmptyItems, final InternalCallContext context) { + private void postEvents(final ImmutableAccountData account, final Invoice invoice, final Set adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithNonEmptyItems, final InternalCallContext context) { final List events = new ArrayList(); if (isRealInvoiceWithNonEmptyItems) { @@ -460,15 +456,18 @@ private void postEvents(final Account account, final Invoice invoice, final Set< } } - private void notifyAccountIfEnabled(final Account account, final Invoice invoice, final boolean isRealInvoiceWithNonEmptyItems, final InternalCallContext context) throws InvoiceApiException { - if (account.isNotifiedForInvoices() && isRealInvoiceWithNonEmptyItems) { + private void notifyAccountIfEnabled(final ImmutableAccountData account, final Invoice invoice, final boolean isRealInvoiceWithNonEmptyItems, final InternalCallContext context) throws InvoiceApiException, AccountApiException { + // Ideally we would retrieve the cached version, all the invoice code has been modified to only use ImmutableAccountData, except for the + // isNotifiedForInvoice piece that should probably live outside of invoice code anyways... (see https://github.com/killbill/killbill-email-notifications-plugin) + final Account fullAccount = accountApi.getAccountById(account.getId(), context); + + if (fullAccount.isNotifiedForInvoices() && isRealInvoiceWithNonEmptyItems) { // Need to re-hydrate the invoice object to get the invoice number (record id) // API_FIX InvoiceNotifier public API? - invoiceNotifier.notify(account, new DefaultInvoice(invoiceDao.getById(invoice.getId(), context)), buildTenantContext(context)); + invoiceNotifier.notify(fullAccount, new DefaultInvoice(invoiceDao.getById(invoice.getId(), context)), buildTenantContext(context)); } } - private InvoiceItem computeCBAOnExistingInvoice(final Invoice invoice, final InternalCallContext context) throws InvoiceApiException { // Transformation to Invoice -> InvoiceModelDao final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice); @@ -558,7 +557,6 @@ public static class SubscriptionNotification { private final DateTime effectiveDate; private final boolean isForNotificationTrigger; - public SubscriptionNotification(final DateTime effectiveDate, final boolean isForNotificationTrigger) { this.effectiveDate = effectiveDate; this.isForNotificationTrigger = isForNotificationTrigger; diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java index 47b003471b..ba2227ee1e 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java @@ -21,9 +21,8 @@ import java.util.UUID; import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications; import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification; import org.killbill.billing.util.timezone.DateAndTimeZoneContext; @@ -69,9 +68,9 @@ public DefaultInvoiceMigrationApi(final AccountInternalApi accountUserApi, @Override public UUID createMigrationInvoice(final UUID accountId, final LocalDate targetDate, final BigDecimal balance, final Currency currency, final CallContext context) { - Account account; + ImmutableAccountData account; try { - account = accountUserApi.getAccountById(accountId, internalCallContextFactory.createInternalTenantContext(accountId, context)); + account = accountUserApi.getImmutableAccountDataById(accountId, internalCallContextFactory.createInternalTenantContext(accountId, context)); } catch (AccountApiException e) { log.warn("Unable to find account for id {}", accountId); return null; diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java index 42d8f7c11c..dfe060f97f 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java @@ -35,6 +35,7 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.Currency; @@ -205,9 +206,9 @@ public Invoice triggerInvoiceGeneration(final UUID accountId, @Nullable final Lo final CallContext context) throws InvoiceApiException { final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(accountId, context); - final Account account; + final ImmutableAccountData account; try { - account = accountUserApi.getAccountById(accountId, internalContext); + account = accountUserApi.getImmutableAccountDataById(accountId, internalContext); } catch (final AccountApiException e) { throw new InvoiceApiException(e, ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, e.toString()); } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java index 64f07ba20a..c9a290ae4c 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java @@ -18,9 +18,7 @@ package org.killbill.billing.invoice.generator; -import java.math.BigDecimal; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; @@ -30,13 +28,12 @@ import org.joda.time.LocalDate; import org.joda.time.Months; import org.killbill.billing.ErrorCode; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItem; -import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates; import org.killbill.billing.invoice.model.DefaultInvoice; import org.killbill.billing.junction.BillingEventSet; @@ -66,7 +63,7 @@ public DefaultInvoiceGenerator(final Clock clock, final InvoiceConfig config, fi * adjusts target date to the maximum invoice target date, if future invoices exist */ @Override - public InvoiceWithMetadata generateInvoice(final Account account, @Nullable final BillingEventSet events, + public InvoiceWithMetadata generateInvoice(final ImmutableAccountData account, @Nullable final BillingEventSet events, @Nullable final List existingInvoices, final LocalDate targetDate, final Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException { diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java index 390afcabd9..940f7465c5 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java @@ -28,7 +28,7 @@ import org.joda.time.LocalDate; import org.killbill.billing.ErrorCode; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.BillingPeriod; @@ -63,7 +63,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator public FixedAndRecurringInvoiceItemGenerator() { } - public List generateItems(final Account account, final UUID invoiceId, final BillingEventSet eventSet, + public List generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet, @Nullable final List existingInvoices, final LocalDate targetDate, final Currency targetCurrency, Map perSubscriptionFutureNotificationDate, final InternalCallContext internalCallContext) throws InvoiceApiException { @@ -165,15 +165,15 @@ private List processRecurringEvent(final UUID invoiceId, final UUID if (rate != null) { final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency); - final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, - accountId, - thisEvent.getSubscription().getBundleId(), - thisEvent.getSubscription().getId(), - thisEvent.getPlan().getName(), - thisEvent.getPlanPhase().getName(), - itemDatum.getStartDate(), itemDatum.getEndDate(), - amount, rate, currency); - items.add(recurringItem); + final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, + accountId, + thisEvent.getSubscription().getBundleId(), + thisEvent.getSubscription().getId(), + thisEvent.getPlan().getName(), + thisEvent.getPlanPhase().getName(), + itemDatum.getStartDate(), itemDatum.getEndDate(), + amount, rate, currency); + items.add(recurringItem); } } updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate); @@ -190,7 +190,6 @@ private List processRecurringEvent(final UUID invoiceId, final UUID return items; } - private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List newProposedItems, final BillingMode billingMode, final Map perSubscriptionFutureNotificationDates) { LocalDate nextNotificationDate = null; diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java index 09f41eee6b..e513a00007 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java @@ -17,13 +17,12 @@ package org.killbill.billing.invoice.generator; import java.util.List; -import java.util.UUID; import javax.annotation.Nullable; import org.joda.time.LocalDate; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.invoice.api.Invoice; @@ -31,6 +30,6 @@ import org.killbill.billing.junction.BillingEventSet; public interface InvoiceGenerator { - InvoiceWithMetadata generateInvoice(Account account, @Nullable BillingEventSet events, @Nullable List existingInvoices, + InvoiceWithMetadata generateInvoice(ImmutableAccountData account, @Nullable BillingEventSet events, @Nullable List existingInvoices, LocalDate targetDate, Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException; } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java index 22b0bac63c..d9b309098b 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java @@ -24,7 +24,7 @@ import javax.annotation.Nullable; import org.joda.time.LocalDate; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.invoice.api.Invoice; @@ -35,7 +35,7 @@ public abstract class InvoiceItemGenerator { - public abstract List generateItems(final Account account, final UUID invoiceId, final BillingEventSet eventSet, + public abstract List generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet, @Nullable final List existingInvoices, final LocalDate targetDate, final Currency targetCurrency, Map perSubscriptionFutureNotificationDate, final InternalCallContext context) throws InvoiceApiException; diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java index 8654de5a3a..b19e77415b 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java @@ -29,7 +29,7 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.BillingMode; import org.killbill.billing.catalog.api.CatalogApiException; @@ -71,7 +71,7 @@ public UsageInvoiceItemGenerator(final RawUsageOptimizer rawUsageOptimizer) { @Override - public List generateItems(final Account account, + public List generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet, @Nullable final List existingInvoices, @@ -94,7 +94,7 @@ public List generateItems(final Account account, while (events.hasNext()) { final BillingEvent event = events.next(); // Skip events that are posterior to the targetDate - final LocalDate eventLocalEffectiveDate = new LocalDate(event.getEffectiveDate(), event.getAccount().getTimeZone()); + final LocalDate eventLocalEffectiveDate = new LocalDate(event.getEffectiveDate(), account.getTimeZone()); if (eventLocalEffectiveDate.isAfter(targetDate)) { continue; } @@ -118,7 +118,7 @@ public boolean apply(@Nullable final Usage input) { final UUID subscriptionId = event.getSubscription().getId(); if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) { - final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); + final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(account.getId(), invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); @@ -131,7 +131,7 @@ public boolean apply(@Nullable final Usage input) { curEvents.add(event); } if (curSubscriptionId != null) { - final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); + final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(account.getId(), invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate()); final List consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId); final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.of()); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java index 5095a1c715..bdda28dd74 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java @@ -69,12 +69,14 @@ public class ContiguousIntervalConsumableInArrear { private final Set unitTypes; private final List rawSubscriptionUsage; private final LocalDate targetDate; + private final UUID accountId; private final UUID invoiceId; private final AtomicBoolean isBuilt; private final LocalDate rawUsageStartDate; - public ContiguousIntervalConsumableInArrear(final Usage usage, final UUID invoiceId, final List rawSubscriptionUsage, final LocalDate targetDate, final LocalDate rawUsageStartDate) { + public ContiguousIntervalConsumableInArrear(final Usage usage, final UUID accountId, final UUID invoiceId, final List rawSubscriptionUsage, final LocalDate targetDate, final LocalDate rawUsageStartDate) { this.usage = usage; + this.accountId = accountId; this.invoiceId = invoiceId; this.unitTypes = getConsumableInArrearUnitTypes(usage); this.rawSubscriptionUsage = rawSubscriptionUsage; @@ -173,7 +175,7 @@ public ConsumableInArrearItemsAndNextNotificationDate computeMissingItemsAndNext LocalDate prevDate = null; for (LocalDate curDate : transitionTimes) { if (prevDate != null) { - InvoiceItem item = new UsageInvoiceItem(invoiceId, getAccountId(), getBundleId(), getSubscriptionId(), getPlanName(), + InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(), getPhaseName(), usage.getName(), prevDate, curDate, BigDecimal.ZERO, getCurrency()); result.add(item); } @@ -201,7 +203,7 @@ public ConsumableInArrearItemsAndNextNotificationDate computeMissingItemsAndNext if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) { final BigDecimal amountToBill = toBeBilledUsage.subtract(billedUsage); if (amountToBill.compareTo(BigDecimal.ZERO) > 0) { - InvoiceItem item = new UsageInvoiceItem(invoiceId, getAccountId(), getBundleId(), getSubscriptionId(), getPlanName(), + InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(), getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), amountToBill, getCurrency()); result.add(item); } @@ -403,9 +405,6 @@ public int getBCD() { return billingEvents.get(0).getBillCycleDayLocal(); } - public UUID getAccountId() { - return billingEvents.get(0).getAccount().getId(); - } public UUID getBundleId() { return billingEvents.get(0).getSubscription().getBundleId(); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java index e2b696553d..9a531a7cf6 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java @@ -68,13 +68,15 @@ public int compare(final RawUsage o1, final RawUsage o2) { } }; + private final UUID accountId; private final UUID invoiceId; private final List subscriptionBillingEvents; private final LocalDate targetDate; private final List rawSubscriptionUsage; private final LocalDate rawUsageStartDate; - public SubscriptionConsumableInArrear(final UUID invoiceId, final List subscriptionBillingEvents, final List rawUsage, final LocalDate targetDate, final LocalDate rawUsageStartDate) { + public SubscriptionConsumableInArrear(final UUID accountId, final UUID invoiceId, final List subscriptionBillingEvents, final List rawUsage, final LocalDate targetDate, final LocalDate rawUsageStartDate) { + this.accountId = accountId; this.invoiceId = invoiceId; this.subscriptionBillingEvents = subscriptionBillingEvents; this.targetDate = targetDate; @@ -136,7 +138,7 @@ public String apply(final Usage input) { // Add inflight usage interval if non existent ContiguousIntervalConsumableInArrear existingInterval = inFlightInArrearUsageIntervals.get(usage.getName()); if (existingInterval == null) { - existingInterval = new ContiguousIntervalConsumableInArrear(usage, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate); + existingInterval = new ContiguousIntervalConsumableInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate); inFlightInArrearUsageIntervals.put(usage.getName(), existingInterval); } // Add billing event for that usage interval diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java index 18d1ec73a3..20a900729c 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java @@ -331,11 +331,6 @@ public BillingEvent createMockBillingEvent(@Nullable final Account account, fina Mockito.when(mockAccount.getTimeZone()).thenReturn(DateTimeZone.UTC); final Account accountOrMockAcount = account != null ? account : mockAccount; return new BillingEvent() { - @Override - public Account getAccount() { - return accountOrMockAcount; - } - @Override public int getBillCycleDayLocal() { return billCycleDayLocal; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java index 8667bb3cb2..74d938e2b2 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java @@ -32,6 +32,7 @@ import org.joda.time.LocalDate; import org.killbill.billing.ErrorCode; import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.DefaultAccount; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.DefaultPrice; import org.killbill.billing.catalog.MockInternationalPrice; @@ -1082,6 +1083,7 @@ public void testGetUnpaidInvoicesByAccountId() throws EntityPersistenceException assertEquals(invoices.size(), 2); } + /* * * this test verifies that immediate changes give the correct results diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java index 7cd34eeedb..4b8be241ac 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java @@ -33,6 +33,7 @@ import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.DefaultAccount; import org.killbill.billing.catalog.DefaultPrice; import org.killbill.billing.catalog.MockInternationalPrice; import org.killbill.billing.catalog.MockPlan; diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java index 1e9e60940a..bff7839621 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java @@ -73,7 +73,7 @@ public void testComputeInArrearUsageInterval() { LocalDate targetDate = new LocalDate(2013, 6, 23); - final SubscriptionConsumableInArrear foo = new SubscriptionConsumableInArrear(invoiceId, billingEvents, ImmutableList.of(), targetDate, new LocalDate(dt1, DateTimeZone.UTC)); + final SubscriptionConsumableInArrear foo = new SubscriptionConsumableInArrear(accountId, invoiceId, billingEvents, ImmutableList.of(), targetDate, new LocalDate(dt1, DateTimeZone.UTC)); final List result = foo.computeInArrearUsageInterval(); assertEquals(result.size(), 3); diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java index 616e852d7e..ac6dfdd265 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java @@ -71,7 +71,7 @@ protected void beforeClass() throws Exception { } protected ContiguousIntervalConsumableInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, List rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) { - final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableInArrear(usage, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate())); + final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate())); for (BillingEvent event : events) { intervalConsumableInArrear.addBillingEvent(event); } @@ -120,7 +120,6 @@ protected BillingEvent createMockBillingEvent(DateTime effectiveDate, BillingPer final Account account = Mockito.mock(Account.class); Mockito.when(account.getId()).thenReturn(accountId); - Mockito.when(result.getAccount()).thenReturn(account); final SubscriptionBase subscription = Mockito.mock(SubscriptionBase.class); Mockito.when(subscription.getId()).thenReturn(subscriptionId); diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java index 66af04cfd0..241cc16ad7 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java @@ -49,12 +49,14 @@ import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; +import org.joda.time.DateTimeZone; import org.killbill.billing.ErrorCode; import org.killbill.billing.ObjectType; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountData; import org.killbill.billing.account.api.AccountEmail; +import org.killbill.billing.account.api.AccountInternalApi; import org.killbill.billing.account.api.AccountUserApi; import org.killbill.billing.account.api.MutableAccountData; import org.killbill.billing.catalog.api.Currency; @@ -152,6 +154,7 @@ public AccountResource(final JaxrsUriBuilder uriBuilder, final AuditUserApi auditUserApi, final CustomFieldUserApi customFieldUserApi, final SubscriptionApi subscriptionApi, + final AccountInternalApi accountInternalApi, final OverdueInternalApi overdueApi, final Clock clock, final PaymentConfig paymentConfig, diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BillCycleDayCalculator.java index 24923f7db2..df27149792 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BillCycleDayCalculator.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BillCycleDayCalculator.java @@ -21,14 +21,12 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.killbill.billing.catalog.api.BillingPeriod; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.killbill.billing.ErrorCode; -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; +import org.killbill.billing.account.api.ImmutableAccountData; +import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.BillingAlignment; +import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.catalog.api.Catalog; import org.killbill.billing.catalog.api.CatalogApiException; import org.killbill.billing.catalog.api.CatalogService; @@ -37,13 +35,13 @@ import org.killbill.billing.catalog.api.PlanPhase; import org.killbill.billing.catalog.api.PlanPhaseSpecifier; import org.killbill.billing.catalog.api.Product; -import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; -import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException; -import org.killbill.billing.subscription.api.SubscriptionBase; -import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle; -import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.events.EffectiveSubscriptionInternalEvent; +import org.killbill.billing.subscription.api.SubscriptionBase; import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi; +import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType; +import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; @@ -61,7 +59,7 @@ public BillCycleDayCalculator(final CatalogService catalogService, final Subscri this.subscriptionApi = subscriptionApi; } - protected int calculateBcd(final UUID bundleId, final SubscriptionBase subscription, final EffectiveSubscriptionInternalEvent transition, final Account account, final InternalCallContext context) + protected int calculateBcd(final ImmutableAccountData account, final int accountBillCycleDayLocal, final UUID bundleId, final SubscriptionBase subscription, final EffectiveSubscriptionInternalEvent transition, final InternalCallContext context) throws CatalogApiException, AccountApiException, SubscriptionBaseApiException { final Catalog catalog = catalogService.getFullCatalog(context); @@ -86,19 +84,16 @@ protected int calculateBcd(final UUID bundleId, final SubscriptionBase subscript phase.getPhaseType()), transition.getRequestedTransitionTime()); - return calculateBcdForAlignment(alignment, bundleId, subscription, account, catalog, plan, context); + return calculateBcdForAlignment(account, accountBillCycleDayLocal, subscription, alignment, bundleId, catalog, plan, context); } @VisibleForTesting - int calculateBcdForAlignment(final BillingAlignment alignment, final UUID bundleId, final SubscriptionBase subscription, - final Account account, final Catalog catalog, final Plan plan, final InternalCallContext context) throws AccountApiException, SubscriptionBaseApiException, CatalogApiException { + int calculateBcdForAlignment(final ImmutableAccountData account, final int accountBillCycleDayLocal, final SubscriptionBase subscription, final BillingAlignment alignment, final UUID bundleId, + final Catalog catalog, final Plan plan, final InternalCallContext context) throws AccountApiException, SubscriptionBaseApiException, CatalogApiException { int result = 0; switch (alignment) { case ACCOUNT: - result = account.getBillCycleDayLocal(); - if (result == 0) { - result = calculateBcdFromSubscription(subscription, plan, account, catalog, context); - } + result = accountBillCycleDayLocal != 0 ? accountBillCycleDayLocal : calculateBcdFromSubscription(subscription, plan, account, catalog, context); break; case BUNDLE: final SubscriptionBase baseSub = subscriptionApi.getBaseSubscription(bundleId, context); @@ -122,7 +117,7 @@ int calculateBcdForAlignment(final BillingAlignment alignment, final UUID bundle } @VisibleForTesting - int calculateBcdFromSubscription(final SubscriptionBase subscription, final Plan plan, final Account account, final Catalog catalog, final InternalCallContext context) + int calculateBcdFromSubscription(final SubscriptionBase subscription, final Plan plan, final ImmutableAccountData account, final Catalog catalog, final InternalCallContext context) throws AccountApiException, CatalogApiException { // Retrieve the initial phase type for that subscription // TODO - this should be extracted somewhere, along with this code above diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java index a55956e17e..c6ee181ca0 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java @@ -89,8 +89,6 @@ public void insertBlockingEvents(final SortedSet billingEvents, fi return; } - final Account account = billingEvents.first().getAccount(); - final Hashtable> bundleMap = createBundleSubscriptionMap(billingEvents); final SortedSet billingEventsToAdd = new TreeSet(); @@ -100,7 +98,7 @@ public void insertBlockingEvents(final SortedSet billingEvents, fi final List blockingDurations = createBlockingDurations(blockingEvents); for (final UUID bundleId : bundleMap.keySet()) { for (final SubscriptionBase subscription : bundleMap.get(bundleId)) { - billingEventsToAdd.addAll(createNewEvents(blockingDurations, billingEvents, account, subscription)); + billingEventsToAdd.addAll(createNewEvents(blockingDurations, billingEvents, subscription)); billingEventsToRemove.addAll(eventsToRemove(blockingDurations, billingEvents, subscription)); } } @@ -133,7 +131,7 @@ protected SortedSet eventsToRemove(final List di return result; } - protected SortedSet createNewEvents(final List disabledDuration, final SortedSet billingEvents, final Account account, final SubscriptionBase subscription) { + protected SortedSet createNewEvents(final List disabledDuration, final SortedSet billingEvents, final SubscriptionBase subscription) { final SortedSet result = new TreeSet(); for (final DisabledDuration duration : disabledDuration) { // The first one before the blocked duration @@ -188,7 +186,6 @@ protected SortedSet filter(final SortedSet billingEv } protected BillingEvent createNewDisableEvent(final DateTime odEventTime, final BillingEvent previousEvent) { - final Account account = previousEvent.getAccount(); final int billCycleDay = previousEvent.getBillCycleDayLocal(); final SubscriptionBase subscription = previousEvent.getSubscription(); final DateTime effectiveDate = odEventTime; @@ -207,7 +204,7 @@ protected BillingEvent createNewDisableEvent(final DateTime odEventTime, final B final Long totalOrdering = globaltotalOrder.getAndIncrement(); final DateTimeZone tz = previousEvent.getTimeZone(); - return new DefaultBillingEvent(account, subscription, effectiveDate, true, plan, planPhase, + return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase, fixedPrice, recurringPrice, currency, billingPeriod, billCycleDay, description, totalOrdering, type, tz); @@ -215,7 +212,6 @@ protected BillingEvent createNewDisableEvent(final DateTime odEventTime, final B protected BillingEvent createNewReenableEvent(final DateTime odEventTime, final BillingEvent previousEvent) { // All fields are populated with the event state from before the blocking period, for invoice to resume invoicing - final Account account = previousEvent.getAccount(); final int billCycleDay = previousEvent.getBillCycleDayLocal(); final SubscriptionBase subscription = previousEvent.getSubscription(); final DateTime effectiveDate = odEventTime; @@ -230,7 +226,7 @@ protected BillingEvent createNewReenableEvent(final DateTime odEventTime, final final Long totalOrdering = globaltotalOrder.getAndIncrement(); final DateTimeZone tz = previousEvent.getTimeZone(); - return new DefaultBillingEvent(account, subscription, effectiveDate, true, plan, planPhase, + return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase, fixedPrice, recurringPrice, currency, billingPeriod, billCycleDay, description, totalOrdering, type, tz); diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java index 1a7f98dadf..cbf1076187 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java @@ -17,14 +17,13 @@ package org.killbill.billing.junction.plumbing.billing; import java.math.BigDecimal; -import java.util.Collections; import java.util.List; import javax.annotation.Nullable; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.catalog.api.Catalog; import org.killbill.billing.catalog.api.CatalogApiException; @@ -42,7 +41,6 @@ public class DefaultBillingEvent implements BillingEvent { - private final Account account; private final int billCycleDayLocal; private final SubscriptionBase subscription; private final DateTime effectiveDate; @@ -59,11 +57,10 @@ public class DefaultBillingEvent implements BillingEvent { private final List usages; - public DefaultBillingEvent(final Account account, final EffectiveSubscriptionInternalEvent transition, final SubscriptionBase subscription, final int billCycleDayLocal, final Currency currency, final Catalog catalog) throws CatalogApiException { + public DefaultBillingEvent(final ImmutableAccountData account, final EffectiveSubscriptionInternalEvent transition, final SubscriptionBase subscription, final int billCycleDayLocal, final Currency currency, final Catalog catalog) throws CatalogApiException { final boolean isActive = transition.getTransitionType() != SubscriptionBaseTransitionType.CANCEL; - this.account = account; this.billCycleDayLocal = billCycleDayLocal; this.subscription = subscription; this.effectiveDate = transition.getEffectiveTransitionTime(); @@ -91,12 +88,11 @@ public DefaultBillingEvent(final Account account, final EffectiveSubscriptionInt this.usages = initializeUsage(isActive); } - public DefaultBillingEvent(final Account account, final SubscriptionBase subscription, final DateTime effectiveDate, final boolean isActive, + public DefaultBillingEvent(final SubscriptionBase subscription, final DateTime effectiveDate, final boolean isActive, final Plan plan, final PlanPhase planPhase, final BigDecimal fixedPrice, final BigDecimal recurringPrice, final Currency currency, final BillingPeriod billingPeriod, final int billCycleDayLocal, final String description, final long totalOrdering, final SubscriptionBaseTransitionType type, final DateTimeZone timeZone) { - this.account = account; this.subscription = subscription; this.effectiveDate = effectiveDate; this.plan = plan; @@ -160,11 +156,6 @@ public int compareTo(final BillingEvent e1) { } } - @Override - public Account getAccount() { - return account; - } - @Override public int getBillCycleDayLocal() { return billCycleDayLocal; @@ -246,7 +237,6 @@ public String toString() { sb.append(", planPhaseName=").append(planPhase.getName()); sb.append(", subscriptionId=").append(subscription.getId()); sb.append(", totalOrdering=").append(totalOrdering); - sb.append(", accountId=").append(account.getId()); sb.append('}'); return sb.toString(); } @@ -265,9 +255,6 @@ public boolean equals(final Object o) { if (billCycleDayLocal != that.billCycleDayLocal) { return false; } - if (account != null ? !account.equals(that.account) : that.account != null) { - return false; - } if (billingPeriod != that.billingPeriod) { return false; } @@ -310,8 +297,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { - int result = account != null ? account.hashCode() : 0; - result = 31 * result + billCycleDayLocal; + int result = 31 * billCycleDayLocal; result = 31 * result + (subscription != null ? subscription.hashCode() : 0); result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0); result = 31 * result + (planPhase != null ? planPhase.hashCode() : 0); diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java index 7eddd1a976..be015309c5 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java @@ -23,10 +23,9 @@ import javax.annotation.Nullable; import org.killbill.billing.ObjectType; -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; -import org.killbill.billing.account.api.MutableAccountData; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.CatalogApiException; import org.killbill.billing.catalog.api.CatalogService; @@ -89,7 +88,7 @@ public BillingEventSet getBillingEventsForAccountAndUpdateAccountBCD(final UUID result.setRecurringBillingMode(currentCatalog.getRecurringBillingMode()); try { - final Account account = accountApi.getAccountById(accountId, context); + final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context); // Check to see if billing is off for the account final List accountTags = tagApi.getTags(accountId, ObjectType.ACCOUNT, context); @@ -123,8 +122,8 @@ private void eventsToString(final StringBuilder stringBuilder, final SortedSet bundles, final Account account, final DryRunArguments dryRunArguments, final InternalCallContext context, - final DefaultBillingEventSet result) throws SubscriptionBaseApiException { + private void addBillingEventsForBundles(final List bundles, final ImmutableAccountData account, final DryRunArguments dryRunArguments, final InternalCallContext context, + final DefaultBillingEventSet result) throws SubscriptionBaseApiException, AccountApiException { final boolean dryRunMode = dryRunArguments != null; @@ -136,14 +135,14 @@ private void addBillingEventsForBundles(final List bundl final UUID fakeBundleId = UUIDs.randomUUID(); final List subscriptions = subscriptionApi.getSubscriptionsForBundle(fakeBundleId, dryRunArguments, context); - addBillingEventsForSubscription(subscriptions, fakeBundleId, account, dryRunMode, context, result); + addBillingEventsForSubscription(account, subscriptions, fakeBundleId, dryRunMode, context, result); } for (final SubscriptionBaseBundle bundle : bundles) { final DryRunArguments dryRunArgumentsForBundle = (dryRunArguments != null && - dryRunArguments.getBundleId() != null && - dryRunArguments.getBundleId().equals(bundle.getId())) ? + dryRunArguments.getBundleId() != null && + dryRunArguments.getBundleId().equals(bundle.getId())) ? dryRunArguments : null; final List subscriptions = subscriptionApi.getSubscriptionsForBundle(bundle.getId(), dryRunArgumentsForBundle, context); @@ -155,18 +154,22 @@ private void addBillingEventsForBundles(final List bundl result.getSubscriptionIdsWithAutoInvoiceOff().add(subscription.getId()); } } else { // billing is not off - addBillingEventsForSubscription(subscriptions, bundle.getId(), account, dryRunMode, context, result); + addBillingEventsForSubscription(account, subscriptions, bundle.getId(), dryRunMode, context, result); } } } - private void addBillingEventsForSubscription(final List subscriptions, final UUID bundleId, final Account account, + private void addBillingEventsForSubscription(final ImmutableAccountData account, + final List subscriptions, + final UUID bundleId, final boolean dryRunMode, final InternalCallContext context, - final DefaultBillingEventSet result) { + final DefaultBillingEventSet result) throws AccountApiException { // If dryRun is specified, we don't want to to update the account BCD value, so we initialize the flag updatedAccountBCD to true boolean updatedAccountBCD = dryRunMode; + + int currentAccountBCD = accountApi.getBCD(account.getId(), context); for (final SubscriptionBase subscription : subscriptions) { // The subscription did not even start, so there is nothing to do yet, we can skip and avoid some NPE down the line when calculating the BCD @@ -176,12 +179,10 @@ private void addBillingEventsForSubscription(final List subscr for (final EffectiveSubscriptionInternalEvent transition : subscriptionApi.getBillingTransitions(subscription, context)) { try { - final int bcdLocal = bcdCalculator.calculateBcd(bundleId, subscription, transition, account, context); + final int bcdLocal = bcdCalculator.calculateBcd(account, currentAccountBCD, bundleId, subscription, transition, context); - if (account.getBillCycleDayLocal() == 0 && !updatedAccountBCD) { - final MutableAccountData modifiedData = account.toMutableAccountData(); - modifiedData.setBillCycleDayLocal(bcdLocal); - accountApi.updateAccount(account.getExternalKey(), modifiedData, context); + if (currentAccountBCD == 0 && !updatedAccountBCD) { + accountApi.updateBCD(account.getExternalKey(), bcdLocal, context); updatedAccountBCD = true; } diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java index 5542ff3a0d..adef503dc8 100644 --- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java +++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java @@ -20,11 +20,11 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; +import org.killbill.billing.account.api.ImmutableAccountData; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.Test; -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.catalog.api.BillingAlignment; import org.killbill.billing.catalog.api.Catalog; @@ -60,10 +60,10 @@ public void testCalculateBCDForAOWithBPCancelledBundleAligned() throws Exception Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.any(), Mockito.any())).thenReturn(plan); Mockito.when(subscription.getLastActivePlan()).thenReturn(plan); - final Account account = Mockito.mock(Account.class); + final ImmutableAccountData account = Mockito.mock(ImmutableAccountData.class); Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone); - final Integer billCycleDayLocal = billCycleDayCalculator.calculateBcdForAlignment(BillingAlignment.BUNDLE, bundle.getId(), subscription, - account, catalog, null, internalCallContext); + final Integer billCycleDayLocal = billCycleDayCalculator.calculateBcdForAlignment(account, 0, subscription, BillingAlignment.BUNDLE, bundle.getId(), + catalog, null, internalCallContext); Assert.assertEquals(billCycleDayLocal, (Integer) expectedBCDUTC); } @@ -132,7 +132,7 @@ private void verifyBCDCalculation(final DateTimeZone accountTimeZone, final Date final Plan plan = Mockito.mock(Plan.class); Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(startDateUTC, null)).thenReturn(startDateUTC); - final Account account = Mockito.mock(Account.class); + final ImmutableAccountData account = Mockito.mock(ImmutableAccountData.class); Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone); final Integer bcd = billCycleDayCalculator.calculateBcdFromSubscription(subscription, plan, account, Mockito.mock(Catalog.class), internalCallContext); diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java index 34ab02f709..63438b7eea 100644 --- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java +++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java @@ -265,6 +265,8 @@ private Account createAccount(final int billCycleDay) throws AccountApiException Mockito.when(account.getId()).thenReturn(UUID.randomUUID()); Mockito.when(account.getTimeZone()).thenReturn(DateTimeZone.UTC); Mockito.when(accountInternalApi.getAccountById(Mockito.any(), Mockito.any())).thenReturn(account); + Mockito.when(accountInternalApi.getImmutableAccountDataById(Mockito.any(), Mockito.any())).thenReturn(account); + Mockito.when(accountInternalApi.getBCD(Mockito.any(), Mockito.any())).thenReturn(billCycleDay); return account; } diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java index b2e8ce6308..e81a1285fa 100644 --- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java +++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java @@ -321,7 +321,7 @@ public void testCreateNewEventsOpenPrev() { disabledDuration.add(new DisabledDuration(now, null)); billingEvents.add(createRealEvent(now.minusDays(1), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 1); assertEquals(results.first().getEffectiveDate(), now); @@ -343,7 +343,7 @@ public void testCreateNewEventsOpenPrevFollow() { billingEvents.add(createRealEvent(now.minusDays(1), subscription1)); billingEvents.add(createRealEvent(now.plusDays(1), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 1); assertEquals(results.first().getEffectiveDate(), now); @@ -364,7 +364,7 @@ public void testCreateNewEventsOpenFollow() { disabledDuration.add(new DisabledDuration(now, null)); billingEvents.add(createRealEvent(now.plusDays(1), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 0); } @@ -380,7 +380,7 @@ public void testCreateNewEventsClosedPrev() { disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); billingEvents.add(createRealEvent(now.minusDays(1), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 2); assertEquals(results.first().getEffectiveDate(), now); @@ -405,7 +405,7 @@ public void testCreateNewEventsClosedPrevBetw() { billingEvents.add(createRealEvent(now.minusDays(1), subscription1)); billingEvents.add(createRealEvent(now.plusDays(1), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 2); assertEquals(results.first().getEffectiveDate(), now); @@ -431,7 +431,7 @@ public void testCreateNewEventsClosedPrevBetwNext() { billingEvents.add(createRealEvent(now.plusDays(1), subscription1)); billingEvents.add(createRealEvent(now.plusDays(3), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 2); assertEquals(results.first().getEffectiveDate(), now); @@ -455,7 +455,7 @@ public void testCreateNewEventsClosedBetwn() { disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); billingEvents.add(createRealEvent(now.plusDays(1), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 1); assertEquals(results.last().getEffectiveDate(), now.plusDays(2)); @@ -474,7 +474,7 @@ public void testCreateNewEventsClosedBetweenFollow() { disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); billingEvents.add(createRealEvent(now.plusDays(1), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 1); assertEquals(results.last().getEffectiveDate(), now.plusDays(2)); @@ -493,7 +493,7 @@ public void testCreateNewEventsClosedFollow() { disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); billingEvents.add(createRealEvent(now.plusDays(3), subscription1)); - final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); + final SortedSet results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 0); } @@ -535,7 +535,7 @@ protected BillingEvent createRealEvent(final DateTime effectiveDate, final Subsc final Long totalOrdering = 0L; final DateTimeZone tz = DateTimeZone.UTC; - return new DefaultBillingEvent(account, subscription, effectiveDate, true, plan, planPhase, + return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase, fixedPrice, recurringPrice, currency, billingPeriod, billCycleDay, description, totalOrdering, type, tz); @@ -605,7 +605,7 @@ public void testCreateNewReenableEvent() { private class MockBillingEvent extends DefaultBillingEvent { public MockBillingEvent() { - super(account, subscription1, clock.getUTCNow(), true, null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL, + super(subscription1, clock.getUTCNow(), true, null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL, 4, "", 3L, SubscriptionBaseTransitionType.CREATE, DateTimeZone.UTC); } } diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java index a6a212504c..aa47d70bb5 100644 --- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java +++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java @@ -172,7 +172,7 @@ public void testEventOrderingMix() { public void testToString() throws Exception { // Simple test to ensure we have an easy to read toString representation final BillingEvent event = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z", DateTimeZone.UTC), SubscriptionBaseTransitionType.CREATE); - Assert.assertEquals(event.toString(), "DefaultBillingEvent{type=CREATE, effectiveDate=2012-01-01T00:02:04.000Z, planPhaseName=Test-trial, subscriptionId=00000000-0000-0000-0000-000000000000, totalOrdering=1, accountId=" + event.getAccount().getId().toString() + "}"); + Assert.assertEquals(event.toString(), "DefaultBillingEvent{type=CREATE, effectiveDate=2012-01-01T00:02:04.000Z, planPhaseName=Test-trial, subscriptionId=00000000-0000-0000-0000-000000000000, totalOrdering=1}"); } private BillingEvent createEvent(final SubscriptionBase sub, final DateTime effectiveDate, final SubscriptionBaseTransitionType type) { @@ -186,7 +186,7 @@ private BillingEvent createEvent(final SubscriptionBase sub, final DateTime effe final PlanPhase shotgunMonthly = createMockMonthlyPlanPhase(null, BigDecimal.ZERO, PhaseType.TRIAL); final Account account = new MockAccountBuilder().build(); - return new DefaultBillingEvent(account, sub, effectiveDate, true, + return new DefaultBillingEvent(sub, effectiveDate, true, shotgun, shotgunMonthly, BigDecimal.ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, billCycleDay, "Test Event 1", totalOrdering, type, DateTimeZone.UTC); diff --git a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java index b5e0d137b0..d676cf9a5e 100644 --- a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java +++ b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java @@ -18,7 +18,7 @@ import org.killbill.billing.ErrorCode; import org.killbill.billing.ObjectType; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.entitlement.api.BlockingStateType; @@ -62,7 +62,7 @@ public DefaultOverdueInternalApi(final OverdueWrapperFactory factory, @SuppressWarnings("unchecked") @Override - public OverdueState getOverdueStateFor(final Account overdueable, final TenantContext context) throws OverdueException { + public OverdueState getOverdueStateFor(final ImmutableAccountData overdueable, final TenantContext context) throws OverdueException { try { final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context); final String stateName = accessApi.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContextFactory.createInternalTenantContext(context)).getStateName(); @@ -75,7 +75,7 @@ public OverdueState getOverdueStateFor(final Account overdueable, final TenantCo } @Override - public BillingState getBillingStateFor(final Account overdueable, final TenantContext context) throws OverdueException { + public BillingState getBillingStateFor(final ImmutableAccountData overdueable, final TenantContext context) throws OverdueException { log.debug("Billing state of of {} requested", overdueable.getId()); final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context); @@ -84,19 +84,19 @@ public BillingState getBillingStateFor(final Account overdueable, final TenantCo } @Override - public OverdueState refreshOverdueStateFor(final Account blockable, final CallContext context) throws OverdueException, OverdueApiException { + public OverdueState refreshOverdueStateFor(final ImmutableAccountData blockable, final CallContext context) throws OverdueException, OverdueApiException { log.info("Refresh of blockable {} ({}) requested", blockable.getId(), blockable.getClass()); final InternalCallContext internalCallContext = createInternalCallContext(blockable, context); final OverdueWrapper wrapper = factory.createOverdueWrapperFor(blockable, internalCallContext); return wrapper.refresh(internalCallContext); } - private InternalCallContext createInternalCallContext(final Account blockable, final CallContext context) { + private InternalCallContext createInternalCallContext(final ImmutableAccountData blockable, final CallContext context) { return internalCallContextFactory.createInternalCallContext(blockable.getId(), ObjectType.ACCOUNT, context); } @Override - public void setOverrideBillingStateForAccount(final Account overdueable, final BillingState state, final CallContext context) { + public void setOverrideBillingStateForAccount(final ImmutableAccountData overdueable, final BillingState state, final CallContext context) { throw new UnsupportedOperationException(); } } diff --git a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java index e84cdb6b05..384686ce5e 100644 --- a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java +++ b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java @@ -34,6 +34,7 @@ import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.catalog.api.BillingActionPolicy; @@ -120,7 +121,7 @@ public OverdueStateApplicator(final BlockingInternalApi accessApi, } public void apply(final OverdueStateSet overdueStateSet, final BillingState billingState, - final Account account, final OverdueState previousOverdueState, + final ImmutableAccountData account, final OverdueState previousOverdueState, final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException, OverdueApiException { try { @@ -157,7 +158,7 @@ public void apply(final OverdueStateSet overdueStateSet, final BillingState bill cancelSubscriptionsIfRequired(account, nextOverdueState, context); - sendEmailIfRequired(billingState, account, nextOverdueState, context); + sendEmailIfRequired(account.getId(), billingState, nextOverdueState, context); avoid_extra_credit_by_toggling_AUTO_INVOICE_OFF(account, previousOverdueState, nextOverdueState, context); @@ -169,6 +170,8 @@ public void apply(final OverdueStateSet overdueStateSet, final BillingState bill if (e.getCode() != ErrorCode.OVERDUE_NO_REEVALUATION_INTERVAL.getCode()) { throw new OverdueException(e); } + } catch (AccountApiException e) { + throw new OverdueException(e); } try { bus.post(createOverdueEvent(account, previousOverdueState.getName(), nextOverdueState.getName(), isBlockBillingTransition(previousOverdueState, nextOverdueState), @@ -178,7 +181,7 @@ public void apply(final OverdueStateSet overdueStateSet, final BillingState bill } } - private void avoid_extra_credit_by_toggling_AUTO_INVOICE_OFF(final Account account, final OverdueState previousOverdueState, + private void avoid_extra_credit_by_toggling_AUTO_INVOICE_OFF(final ImmutableAccountData account, final OverdueState previousOverdueState, final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueApiException { if (isBlockBillingTransition(previousOverdueState, nextOverdueState)) { set_AUTO_INVOICE_OFF_on_blockedBilling(account.getId(), context); @@ -187,7 +190,7 @@ private void avoid_extra_credit_by_toggling_AUTO_INVOICE_OFF(final Account accou } } - public void clear(final Account account, final OverdueState previousOverdueState, final OverdueState clearState, final InternalCallContext context) throws OverdueException { + public void clear(final ImmutableAccountData account, final OverdueState previousOverdueState, final OverdueState clearState, final InternalCallContext context) throws OverdueException { log.debug("OverdueStateApplicator:clear : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueState.getName()); @@ -209,13 +212,13 @@ public void clear(final Account account, final OverdueState previousOverdueState } } - private OverdueChangeInternalEvent createOverdueEvent(final Account overdueable, final String previousOverdueStateName, final String nextOverdueStateName, + private OverdueChangeInternalEvent createOverdueEvent(final ImmutableAccountData overdueable, final String previousOverdueStateName, final String nextOverdueStateName, final boolean isBlockedBilling, final boolean isUnblockedBilling, final InternalCallContext context) throws BlockingApiException { return new DefaultOverdueChangeEvent(overdueable.getId(), previousOverdueStateName, nextOverdueStateName, isBlockedBilling, isUnblockedBilling, context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()); } - protected void storeNewState(final Account blockable, final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException { + protected void storeNewState(final ImmutableAccountData blockable, final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException { try { blockingApi.setBlockingState(new DefaultBlockingState(blockable.getId(), BlockingStateType.ACCOUNT, @@ -269,17 +272,17 @@ private boolean blockEntitlement(final OverdueState nextOverdueState) { return nextOverdueState.isDisableEntitlementAndChangesBlocked(); } - protected void createFutureNotification(final Account account, final DateTime timeOfNextCheck, final InternalCallContext context) { + protected void createFutureNotification(final ImmutableAccountData account, final DateTime timeOfNextCheck, final InternalCallContext context) { final OverdueCheckNotificationKey notificationKey = new OverdueCheckNotificationKey(account.getId()); checkPoster.insertOverdueNotification(account.getId(), timeOfNextCheck, OverdueCheckNotifier.OVERDUE_CHECK_NOTIFIER_QUEUE, notificationKey, context); } - protected void clearFutureNotification(final Account account, final InternalCallContext context) { + protected void clearFutureNotification(final ImmutableAccountData account, final InternalCallContext context) { // Need to clear the override table here too (when we add it) checkPoster.clearOverdueCheckNotifications(account.getId(), OverdueCheckNotifier.OVERDUE_CHECK_NOTIFIER_QUEUE, OverdueCheckNotificationKey.class, context); } - private void cancelSubscriptionsIfRequired(final Account account, final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException { + private void cancelSubscriptionsIfRequired(final ImmutableAccountData account, final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException { if (nextOverdueState.getOverdueCancellationPolicy() == OverdueCancellationPolicy.NONE) { return; } @@ -315,7 +318,7 @@ private void cancelSubscriptionsIfRequired(final Account account, final OverdueS } } - private void computeEntitlementsToCancel(final Account account, final List result, final CallContext context) throws EntitlementApiException { + private void computeEntitlementsToCancel(final ImmutableAccountData account, final List result, final CallContext context) throws EntitlementApiException { final List allEntitlementsForAccountId = entitlementApi.getAllEntitlementsForAccountId(account.getId(), context); // Entitlement is smart enough and will cancel the associated add-ons. See also discussion in https://github.com/killbill/killbill/issues/94 final Collection allEntitlementsButAddonsForAccountId = Collections2.filter(allEntitlementsForAccountId, @@ -329,8 +332,8 @@ public boolean apply(final Entitlement entitlement) { result.addAll(allEntitlementsButAddonsForAccountId); } - private void sendEmailIfRequired(final BillingState billingState, final Account account, - final OverdueState nextOverdueState, final InternalTenantContext context) { + private void sendEmailIfRequired(final UUID accountId, final BillingState billingState, + final OverdueState nextOverdueState, final InternalTenantContext context) throws AccountApiException { // Note: we don't want to fail the full refresh call because sending the email failed. // That's the reason why we catch all exceptions here. // The alternative would be to: throw new OverdueApiException(e, ErrorCode.EMAIL_SENDING_FAILED); @@ -340,6 +343,7 @@ private void sendEmailIfRequired(final BillingState billingState, final Account return; } + final Account account = accountApi.getAccountById(accountId, context); if (Strings.emptyToNull(account.getEmail()) == null) { log.warn("Unable to send overdue notification email for account {} and overdueable {}: no email specified", account.getId(), account.getId()); return; diff --git a/overdue/src/main/java/org/killbill/billing/overdue/calculator/BillingStateCalculator.java b/overdue/src/main/java/org/killbill/billing/overdue/calculator/BillingStateCalculator.java index 734264b743..7d594fce73 100644 --- a/overdue/src/main/java/org/killbill/billing/overdue/calculator/BillingStateCalculator.java +++ b/overdue/src/main/java/org/killbill/billing/overdue/calculator/BillingStateCalculator.java @@ -26,16 +26,15 @@ import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; - -import org.killbill.billing.account.api.Account; -import org.killbill.billing.payment.api.PaymentResponse; -import org.killbill.clock.Clock; +import org.killbill.billing.account.api.ImmutableAccountData; +import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.invoice.api.Invoice; +import org.killbill.billing.invoice.api.InvoiceInternalApi; import org.killbill.billing.overdue.config.api.BillingState; import org.killbill.billing.overdue.config.api.OverdueException; -import org.killbill.billing.callcontext.InternalTenantContext; -import org.killbill.billing.invoice.api.InvoiceInternalApi; +import org.killbill.billing.payment.api.PaymentResponse; import org.killbill.billing.util.tag.Tag; +import org.killbill.clock.Clock; import com.google.inject.Inject; @@ -63,7 +62,7 @@ public BillingStateCalculator(final InvoiceInternalApi invoiceApi, final Clock c this.clock = clock; } - public BillingState calculateBillingState(final Account account, final InternalTenantContext context) throws OverdueException { + public BillingState calculateBillingState(final ImmutableAccountData account, final InternalTenantContext context) throws OverdueException { final SortedSet unpaidInvoices = unpaidInvoicesForAccount(account.getId(), account.getTimeZone(), context); final int numberOfUnpaidInvoices = unpaidInvoices.size(); @@ -78,7 +77,6 @@ public BillingState calculateBillingState(final Account account, final InternalT final PaymentResponse responseForLastFailedPayment = PaymentResponse.INSUFFICIENT_FUNDS; //TODO MDW final Tag[] tags = new Tag[]{}; //TODO MDW - return new BillingState(account.getId(), numberOfUnpaidInvoices, unpaidInvoiceBalance, dateOfEarliestUnpaidInvoice, account.getTimeZone(), idOfEarliestUnpaidInvoice, responseForLastFailedPayment, tags); } diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java index 1439ae5565..0b4560180a 100644 --- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java +++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java @@ -16,7 +16,7 @@ package org.killbill.billing.overdue.wrapper; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.entitlement.api.BlockingStateType; @@ -33,14 +33,15 @@ public class OverdueWrapper { - private final Account overdueable; + private final ImmutableAccountData overdueable; private final BlockingInternalApi api; private final Clock clock; private final OverdueStateSet overdueStateSet; private final BillingStateCalculator billingStateCalcuator; private final OverdueStateApplicator overdueStateApplicator; - public OverdueWrapper(final Account overdueable, final BlockingInternalApi api, + public OverdueWrapper(final ImmutableAccountData overdueable, + final BlockingInternalApi api, final OverdueStateSet overdueStateSet, final Clock clock, final BillingStateCalculator billingStateCalcuator, diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java index c224c83d6f..9c51f85407 100644 --- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java +++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java @@ -19,12 +19,11 @@ import java.util.UUID; import org.joda.time.Period; -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.junction.BlockingInternalApi; -import org.killbill.billing.overdue.OverdueService; import org.killbill.billing.overdue.api.OverdueApiException; import org.killbill.billing.overdue.api.OverdueConfig; import org.killbill.billing.overdue.applicator.OverdueStateApplicator; @@ -65,8 +64,9 @@ public OverdueWrapperFactory(final BlockingInternalApi api, final Clock clock, this.clock = clock; this.overdueConfigCache = overdueConfigCache; } + @SuppressWarnings("unchecked") - public OverdueWrapper createOverdueWrapperFor(final Account blockable, final InternalTenantContext context) throws OverdueException { + public OverdueWrapper createOverdueWrapperFor(final ImmutableAccountData blockable, final InternalTenantContext context) throws OverdueException { return (OverdueWrapper) new OverdueWrapper(blockable, api, getOverdueStateSet(context), clock, billingStateCalculator, overdueStateApplicator); } @@ -75,7 +75,7 @@ public OverdueWrapper createOverdueWrapperFor(final Account blockable, final Int public OverdueWrapper createOverdueWrapperFor(final UUID id, final InternalTenantContext context) throws OverdueException { try { - final Account account = accountApi.getAccountById(id, context); + final ImmutableAccountData account = accountApi.getImmutableAccountDataById(id, context); return new OverdueWrapper(account, api, getOverdueStateSet(context), clock, billingStateCalculator, overdueStateApplicator); } catch (AccountApiException e) { @@ -83,7 +83,6 @@ public OverdueWrapper createOverdueWrapperFor(final UUID id, final InternalTenan } } - private OverdueStateSet getOverdueStateSet(final InternalTenantContext context) throws OverdueException { final OverdueConfig overdueConfig; try { diff --git a/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java b/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java index b8ebdb2bde..bb01c4dbc4 100644 --- a/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java +++ b/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java @@ -23,11 +23,11 @@ import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.overdue.api.OverdueState; import org.mockito.Mockito; import org.testng.Assert; -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceItem; @@ -116,13 +116,13 @@ public void checkStateApplied(final BlockingState result, final OverdueState sta Assert.assertEquals(result.isBlockBilling(), state.isDisableEntitlementAndChangesBlocked()); } - public Account createAccount(final LocalDate dateOfLastUnPaidInvoice) throws SubscriptionBaseApiException, AccountApiException { + public ImmutableAccountData createImmutableAccountData(final LocalDate dateOfLastUnPaidInvoice) throws SubscriptionBaseApiException, AccountApiException { final UUID accountId = UUID.randomUUID(); - final Account account = Mockito.mock(Account.class); + final ImmutableAccountData account = Mockito.mock(ImmutableAccountData.class); Mockito.when(account.getId()).thenReturn(accountId); Mockito.when(account.getTimeZone()).thenReturn(DateTimeZone.UTC); - Mockito.when(accountInternalApi.getAccountById(Mockito.eq(account.getId()), Mockito.any())).thenReturn(account); + Mockito.when(accountInternalApi.getImmutableAccountDataById(Mockito.eq(account.getId()), Mockito.any())).thenReturn(account); final Invoice invoice = Mockito.mock(Invoice.class); Mockito.when(invoice.getInvoiceDate()).thenReturn(dateOfLastUnPaidInvoice); diff --git a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java index ac23e0ae4e..5c77f5c09d 100644 --- a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java +++ b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java @@ -22,13 +22,13 @@ import java.util.UUID; import java.util.concurrent.Callable; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.overdue.api.OverdueState; import org.killbill.billing.overdue.config.DefaultOverdueConfig; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.Test; -import org.killbill.billing.account.api.Account; import org.killbill.billing.overdue.OverdueTestSuiteWithEmbeddedDB; import org.killbill.billing.overdue.config.api.OverdueStateSet; import org.killbill.xmlloader.XMLLoader; @@ -45,7 +45,7 @@ public void testApplicator() throws Exception { final InputStream is = new ByteArrayInputStream(testOverdueHelper.getConfigXml().getBytes()); final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class); - final Account account = Mockito.mock(Account.class); + final ImmutableAccountData account = Mockito.mock(ImmutableAccountData.class); Mockito.when(account.getId()).thenReturn(UUID.randomUUID()); final OverdueStateSet overdueStateSet = config.getOverdueStatesAccount(); diff --git a/overdue/src/test/java/org/killbill/billing/overdue/calculator/TestBillingStateCalculator.java b/overdue/src/test/java/org/killbill/billing/overdue/calculator/TestBillingStateCalculator.java index 46679929d6..d8f7b9d35c 100644 --- a/overdue/src/test/java/org/killbill/billing/overdue/calculator/TestBillingStateCalculator.java +++ b/overdue/src/test/java/org/killbill/billing/overdue/calculator/TestBillingStateCalculator.java @@ -25,6 +25,7 @@ import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; +import org.killbill.billing.account.api.ImmutableAccountData; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.BeforeMethod; @@ -61,7 +62,7 @@ public BillingStateCalculator createBSCalc() { return new BillingStateCalculator(invoiceApi, clock) { @Override - public BillingState calculateBillingState(final Account overdueable, + public BillingState calculateBillingState(final ImmutableAccountData overdueable, final InternalTenantContext context) { return null; } diff --git a/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java b/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java index 927af22fca..c6e22312c6 100644 --- a/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java +++ b/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java @@ -19,7 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; -import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.junction.DefaultBlockingState; import org.killbill.billing.overdue.OverdueTestSuiteWithEmbeddedDB; import org.killbill.billing.overdue.api.OverdueState; @@ -44,24 +44,24 @@ public void testWrapperBasic() throws Exception { final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class); ((MockOverdueConfigCache) overdueConfigCache).loadOverwriteDefaultOverdueConfig(config); - Account account; + ImmutableAccountData account; OverdueWrapper wrapper; OverdueState state; state = config.getOverdueStatesAccount().findState("OD1"); - account = testOverdueHelper.createAccount(clock.getUTCToday().minusDays(31)); + account = testOverdueHelper.createImmutableAccountData(clock.getUTCToday().minusDays(31)); wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext); wrapper.refresh(internalCallContext); testOverdueHelper.checkStateApplied(state); state = config.getOverdueStatesAccount().findState("OD2"); - account = testOverdueHelper.createAccount(clock.getUTCToday().minusDays(41)); + account = testOverdueHelper.createImmutableAccountData(clock.getUTCToday().minusDays(41)); wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext); wrapper.refresh(internalCallContext); testOverdueHelper.checkStateApplied(state); state = config.getOverdueStatesAccount().findState("OD3"); - account = testOverdueHelper.createAccount(clock.getUTCToday().minusDays(51)); + account = testOverdueHelper.createImmutableAccountData(clock.getUTCToday().minusDays(51)); wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext); wrapper.refresh(internalCallContext); testOverdueHelper.checkStateApplied(state); @@ -70,14 +70,14 @@ public void testWrapperBasic() throws Exception { @Test(groups = "slow") public void testWrapperNoConfig() throws Exception { - final Account account; + final ImmutableAccountData account; final OverdueWrapper wrapper; final OverdueState state; final InputStream is = new ByteArrayInputStream(testOverdueHelper.getConfigXml().getBytes()); final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class); state = config.getOverdueStatesAccount().findState(DefaultBlockingState.CLEAR_STATE_NAME); - account = testOverdueHelper.createAccount(clock.getUTCToday().minusDays(31)); + account = testOverdueHelper.createImmutableAccountData(clock.getUTCToday().minusDays(31)); wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext); final OverdueState result = wrapper.refresh(internalCallContext); diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java index 2d4a157705..4803871d01 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java @@ -18,16 +18,14 @@ package org.killbill.billing.payment.core.janitor; import java.io.IOException; -import java.util.List; -import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.DefaultCallContext; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.events.PaymentInternalEvent; import org.killbill.billing.osgi.api.OSGIServiceRegistration; -import org.killbill.billing.payment.core.ProcessorBase; import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper; import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper; import org.killbill.billing.payment.dao.PaymentDao; @@ -123,7 +121,7 @@ public interface JanitorIterationCallback { protected T doJanitorOperationWithAccountLock(final JanitorIterationCallback callback, final InternalTenantContext internalTenantContext) { GlobalLock lock = null; try { - final Account account = accountInternalApi.getAccountByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext); + final ImmutableAccountData account = accountInternalApi.getImmutableAccountDataByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext); lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), account.getExternalKey(), paymentConfig.getMaxGlobalLockRetries()); return callback.doIteration(); } catch (AccountApiException e) { diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java index 1ea86bd885..51788f77bd 100644 --- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java +++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java @@ -227,13 +227,13 @@ public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext p } } - public void process_AUTO_PAY_OFF_removal(final Account account, final InternalCallContext internalCallContext) { - final List entries = controlDao.getAutoPayOffEntry(account.getId()); + public void process_AUTO_PAY_OFF_removal(final UUID accountId, final InternalCallContext internalCallContext) { + final List entries = controlDao.getAutoPayOffEntry(accountId); for (final PluginAutoPayOffModelDao cur : entries) { // TODO In theory we should pass not only PLUGIN_NAME, but also all the plugin list associated which the original call - retryServiceScheduler.scheduleRetry(ObjectType.ACCOUNT, account.getId(), cur.getAttemptId(), internalCallContext.getTenantRecordId(), ImmutableList.of(PLUGIN_NAME), clock.getUTCNow()); + retryServiceScheduler.scheduleRetry(ObjectType.ACCOUNT, accountId, cur.getAttemptId(), internalCallContext.getTenantRecordId(), ImmutableList.of(PLUGIN_NAME), clock.getUTCNow()); } - controlDao.removeAutoPayOffEntry(account.getId()); + controlDao.removeAutoPayOffEntry(accountId); } private UUID getInvoiceId(final Iterable pluginProperties) throws PaymentControlApiException { diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/PaymentTagHandler.java b/payment/src/main/java/org/killbill/billing/payment/invoice/PaymentTagHandler.java index 4113349f9b..6270c21d58 100644 --- a/payment/src/main/java/org/killbill/billing/payment/invoice/PaymentTagHandler.java +++ b/payment/src/main/java/org/killbill/billing/payment/invoice/PaymentTagHandler.java @@ -21,13 +21,11 @@ import java.util.UUID; import org.killbill.billing.ObjectType; -import org.killbill.billing.account.api.Account; -import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; import org.killbill.billing.callcontext.InternalCallContext; +import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.events.ControlTagDeletionInternalEvent; import org.killbill.billing.osgi.api.OSGIServiceRegistration; -import org.killbill.billing.control.plugin.api.PaymentControlPluginApi; import org.killbill.billing.util.callcontext.CallOrigin; import org.killbill.billing.util.callcontext.InternalCallContextFactory; import org.killbill.billing.util.callcontext.UserType; @@ -66,14 +64,9 @@ public void process_AUTO_PAY_OFF_removal(final ControlTagDeletionInternalEvent e } private void processUnpaid_AUTO_PAY_OFF_payments(final UUID accountId, final Long accountRecordId, final Long tenantRecordId, final UUID userToken) { - try { - final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, - "PaymentRequestProcessor", CallOrigin.INTERNAL, UserType.SYSTEM, userToken); - final Account account = accountApi.getAccountById(accountId, internalCallContext); - ((InvoicePaymentControlPluginApi) invoicePaymentControlPlugin).process_AUTO_PAY_OFF_removal(account, internalCallContext); + final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, + "PaymentRequestProcessor", CallOrigin.INTERNAL, UserType.SYSTEM, userToken); + ((InvoicePaymentControlPluginApi) invoicePaymentControlPlugin).process_AUTO_PAY_OFF_removal(accountId, internalCallContext); - } catch (final AccountApiException e) { - log.warn(String.format("Failed to process process removal AUTO_PAY_OFF for account %s", accountId), e); - } } } diff --git a/pom.xml b/pom.xml index 553510eae7..b6500b0d60 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.45 + 0.46-SNAPSHOT killbill 0.15.6-SNAPSHOT From 86379933aa486c9e81cef2ab3113a68d2dced360 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 2 Oct 2015 13:01:13 -0700 Subject: [PATCH 103/137] Introduce 2 new ehcaches to take care of immutable account data and BCD (updated only once). The code also refactors the account API to make sure any path (recordId, id, externalKey) whether from public or private API will end up setting the cache. --- .../api/svcs/DefaultAccountInternalApi.java | 102 ++++++++++++------ .../api/user/DefaultAccountApiBase.java | 84 +++++++++++++++ .../api/user/DefaultAccountUserApi.java | 44 ++++---- .../billing/account/dao/AccountDao.java | 18 ++-- .../billing/account/dao/AccountSqlDao.java | 13 ++- .../account/dao/DefaultAccountDao.java | 9 ++ .../billing/account/dao/AccountSqlDao.sql.stg | 6 ++ .../TestDefaultAccountUserApiWithMocks.java | 2 +- .../billing/account/dao/MockAccountDao.java | 6 ++ .../account/api/AccountInternalApi.java | 2 - .../integration/TestIntegrationBase.java | 5 - .../beatrix/integration/TestPublicBus.java | 2 - .../glue/TestUsageModuleWithEmbeddedDB.java | 4 + .../util/cache/AccountBCDCacheLoader.java | 59 ++++++++++ .../killbill/billing/util/cache/Cachable.java | 38 ++++--- .../billing/util/cache/CacheController.java | 12 ++- .../cache/EhCacheBasedCacheController.java | 29 +++-- .../cache/EhCacheCacheManagerProvider.java | 4 + .../cache/ImmutableAccountCacheLoader.java | 57 ++++++++++ .../util/cache/TenantCatalogCacheLoader.java | 1 - util/src/main/resources/ehcache.xml | 26 +++++ .../GuicyKillbillTestSuiteWithEmbeddedDB.java | 6 ++ .../util/UtilTestSuiteWithEmbeddedDB.java | 4 - 23 files changed, 427 insertions(+), 106 deletions(-) create mode 100644 account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java create mode 100644 util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java create mode 100644 util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java index 3b918f674a..41791b62c1 100644 --- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java +++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java @@ -22,52 +22,72 @@ import javax.inject.Inject; import org.killbill.billing.ErrorCode; +import org.killbill.billing.ObjectType; import org.killbill.billing.account.api.Account; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountEmail; import org.killbill.billing.account.api.AccountInternalApi; -import org.killbill.billing.account.api.DefaultAccount; import org.killbill.billing.account.api.DefaultAccountEmail; import org.killbill.billing.account.api.DefaultImmutableAccountData; +import org.killbill.billing.account.api.DefaultMutableAccountData; import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.account.api.MutableAccountData; +import org.killbill.billing.account.api.user.DefaultAccountApiBase; import org.killbill.billing.account.dao.AccountDao; import org.killbill.billing.account.dao.AccountEmailModelDao; import org.killbill.billing.account.dao.AccountModelDao; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; +import org.killbill.billing.util.cache.AccountBCDCacheLoader; +import org.killbill.billing.util.cache.Cachable.CacheType; +import org.killbill.billing.util.cache.CacheController; +import org.killbill.billing.util.cache.CacheControllerDispatcher; +import org.killbill.billing.util.cache.CacheLoaderArgument; +import org.killbill.billing.util.cache.ImmutableAccountCacheLoader.LoaderCallback; +import org.killbill.billing.util.dao.NonEntityDao; import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; -public class DefaultAccountInternalApi implements AccountInternalApi { +public class DefaultAccountInternalApi extends DefaultAccountApiBase implements AccountInternalApi { private final AccountDao accountDao; + private final CacheControllerDispatcher cacheControllerDispatcher; + private final CacheController accountCacheController; + private final CacheController bcdCacheController; + private final NonEntityDao nonEntityDao; @Inject - public DefaultAccountInternalApi(final AccountDao accountDao) { + public DefaultAccountInternalApi(final AccountDao accountDao, + final NonEntityDao nonEntityDao, + final CacheControllerDispatcher cacheControllerDispatcher) { + super(accountDao, nonEntityDao, cacheControllerDispatcher); this.accountDao = accountDao; + this.nonEntityDao = nonEntityDao; + this.cacheControllerDispatcher = cacheControllerDispatcher; + this.accountCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE); + this.bcdCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_BCD); } @Override public Account getAccountById(final UUID accountId, final InternalTenantContext context) throws AccountApiException { - final AccountModelDao account = accountDao.getById(accountId, context); - if (account == null) { - throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId); - } - return new DefaultAccount(account); + return super.getAccountById(accountId, context); + } + + @Override + public Account getAccountByKey(final String key, final InternalTenantContext context) throws AccountApiException { + return super.getAccountByKey(key, context); } @Override public Account getAccountByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException { - final AccountModelDao accountModelDao = getAccountModelDaoByRecordId(recordId, context); - return new DefaultAccount(accountModelDao); + return super.getAccountByRecordId(recordId, context); } @Override public void updateBCD(final String externalKey, final int bcd, - final InternalCallContext context) throws AccountApiException { + final InternalCallContext context) throws AccountApiException { final Account currentAccount = getAccountByKey(externalKey, context); if (currentAccount == null) { throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey); @@ -76,13 +96,15 @@ public void updateBCD(final String externalKey, final int bcd, final MutableAccountData mutableAccountData = currentAccount.toMutableAccountData(); mutableAccountData.setBillCycleDayLocal(bcd); final AccountModelDao accountToUpdate = new AccountModelDao(currentAccount.getId(), mutableAccountData); + bcdCacheController.putIfAbsent(currentAccount.getId(), new Integer(bcd)); accountDao.update(accountToUpdate, context); } @Override public int getBCD(final UUID accountId, final InternalTenantContext context) throws AccountApiException { - final Account account = getAccountById(accountId, context); - return account.getBillCycleDayLocal(); + final CacheLoaderArgument arg = createBCDCacheLoaderArgument(context); + final Integer result = (Integer) bcdCacheController.get(accountId, arg); + return result != null ? result : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL; } @Override @@ -97,15 +119,6 @@ public AccountEmail apply(final AccountEmailModelDao input) { })); } - @Override - public Account getAccountByKey(final String key, final InternalTenantContext context) throws AccountApiException { - final AccountModelDao accountModelDao = accountDao.getAccountByKey(key, context); - if (accountModelDao == null) { - throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key); - } - return new DefaultAccount(accountModelDao); - } - @Override public void removePaymentMethod(final UUID accountId, final InternalCallContext context) throws AccountApiException { updatePaymentMethod(accountId, null, context); @@ -125,20 +138,14 @@ public UUID getByRecordId(final Long recordId, final InternalTenantContext conte @Override public ImmutableAccountData getImmutableAccountDataById(final UUID accountId, final InternalTenantContext context) throws AccountApiException { - final Account account = getAccountById(accountId, context); - return new DefaultImmutableAccountData(account); - } - - @Override - public ImmutableAccountData getImmutableAccountDataByKey(final String key, final InternalTenantContext context) throws AccountApiException { - final Account account = getAccountByKey(key, context); - return new DefaultImmutableAccountData(account); + final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID)); + return getImmutableAccountDataByRecordId(recordId, context); } @Override public ImmutableAccountData getImmutableAccountDataByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException { - final Account account = getAccountByRecordId(recordId, context); - return new DefaultImmutableAccountData(account); + final CacheLoaderArgument arg = createImmutableAccountCacheLoaderArgument(context); + return (ImmutableAccountData) accountCacheController.get(recordId, arg); } private AccountModelDao getAccountModelDaoByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException { @@ -149,5 +156,36 @@ private AccountModelDao getAccountModelDaoByRecordId(final Long recordId, final return accountModelDao; } + private int getBCDInternal(final UUID accountId, final InternalTenantContext context) { + final Long bcd = accountDao.getAccountBCD(accountId, context); + return bcd != null ? bcd.intValue() : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL; + } + + private CacheLoaderArgument createImmutableAccountCacheLoaderArgument(final InternalTenantContext context) { + final LoaderCallback loaderCallback = new LoaderCallback() { + @Override + public Object loadAccount(final Long recordId, final InternalTenantContext context) { + final Account account = getAccountByRecordIdInternal(recordId, context); + return account != null ? new DefaultImmutableAccountData(account) : null; + } + }; + final Object[] args = new Object[1]; + args[0] = loaderCallback; + final ObjectType irrelevant = null; + return new CacheLoaderArgument(irrelevant, args, context); + } + private CacheLoaderArgument createBCDCacheLoaderArgument(final InternalTenantContext context) { + final AccountBCDCacheLoader.LoaderCallback loaderCallback = new AccountBCDCacheLoader.LoaderCallback() { + @Override + public Object loadAccountBCD(final UUID accountId, final InternalTenantContext context) { + int bcd = getBCDInternal(accountId, context); + return new Integer(bcd); + } + }; + final Object[] args = new Object[1]; + args[0] = loaderCallback; + final ObjectType irrelevant = null; + return new CacheLoaderArgument(irrelevant, args, context); + } } diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java new file mode 100644 index 0000000000..7792ab4afc --- /dev/null +++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java @@ -0,0 +1,84 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.account.api.user; + +import java.util.UUID; + +import org.killbill.billing.ErrorCode; +import org.killbill.billing.ObjectType; +import org.killbill.billing.account.api.Account; +import org.killbill.billing.account.api.AccountApiException; +import org.killbill.billing.account.api.DefaultAccount; +import org.killbill.billing.account.api.DefaultImmutableAccountData; +import org.killbill.billing.account.dao.AccountDao; +import org.killbill.billing.account.dao.AccountModelDao; +import org.killbill.billing.callcontext.InternalTenantContext; +import org.killbill.billing.util.cache.Cachable.CacheType; +import org.killbill.billing.util.cache.CacheController; +import org.killbill.billing.util.cache.CacheControllerDispatcher; +import org.killbill.billing.util.dao.NonEntityDao; + +public class DefaultAccountApiBase { + + private final AccountDao accountDao; + private final CacheControllerDispatcher cacheControllerDispatcher; + private final CacheController accountCacheController; + private final NonEntityDao nonEntityDao; + + public DefaultAccountApiBase(final AccountDao accountDao, + final NonEntityDao nonEntityDao, + final CacheControllerDispatcher cacheControllerDispatcher) { + this.accountDao = accountDao; + this.nonEntityDao = nonEntityDao; + this.cacheControllerDispatcher = cacheControllerDispatcher; + this.accountCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE); + } + + protected Account getAccountById(final UUID accountId, final InternalTenantContext context) throws AccountApiException { + final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID)); + final Account account = getAccountByRecordIdInternal(recordId, context); + if (account == null) { + throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId); + } + accountCacheController.putIfAbsent(accountId, new DefaultImmutableAccountData(account)); + return account; + } + + protected Account getAccountByKey(final String key, final InternalTenantContext context) throws AccountApiException { + final AccountModelDao accountModelDao = accountDao.getAccountByKey(key, context); + if (accountModelDao == null) { + throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key); + } + final Account account = new DefaultAccount(accountModelDao); + accountCacheController.putIfAbsent(account.getId(), new DefaultImmutableAccountData(account)); + return account; + } + + protected Account getAccountByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException { + final Account account = getAccountByRecordIdInternal(recordId, context); + if (account == null) { + throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_RECORD_ID, recordId); + } + return account; + } + + protected Account getAccountByRecordIdInternal(final Long recordId, final InternalTenantContext context) { + final AccountModelDao accountModelDao = accountDao.getByRecordId(recordId, context); + return accountModelDao != null ? new DefaultAccount(accountModelDao) : null; + } +} diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java index 55fb71b7ac..b137cc04b0 100644 --- a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java +++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java @@ -32,9 +32,12 @@ import org.killbill.billing.account.dao.AccountDao; import org.killbill.billing.account.dao.AccountEmailModelDao; import org.killbill.billing.account.dao.AccountModelDao; +import org.killbill.billing.callcontext.InternalTenantContext; +import org.killbill.billing.util.cache.CacheControllerDispatcher; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.InternalCallContextFactory; import org.killbill.billing.util.callcontext.TenantContext; +import org.killbill.billing.util.dao.NonEntityDao; import org.killbill.billing.util.entity.Pagination; import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder; @@ -45,17 +48,35 @@ import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException; -public class DefaultAccountUserApi implements AccountUserApi { +public class DefaultAccountUserApi extends DefaultAccountApiBase implements AccountUserApi { private final InternalCallContextFactory internalCallContextFactory; private final AccountDao accountDao; @Inject - public DefaultAccountUserApi(final InternalCallContextFactory internalCallContextFactory, final AccountDao accountDao) { + public DefaultAccountUserApi(final AccountDao accountDao, + final NonEntityDao nonEntityDao, + final CacheControllerDispatcher cacheControllerDispatcher, + final InternalCallContextFactory internalCallContextFactory) { + super(accountDao, nonEntityDao, cacheControllerDispatcher); this.internalCallContextFactory = internalCallContextFactory; this.accountDao = accountDao; } + + @Override + public Account getAccountByKey(final String key, final TenantContext context) throws AccountApiException { + final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context); + return getAccountByKey(key, internalTenantContext); + } + + @Override + public Account getAccountById(final UUID id, final TenantContext context) throws AccountApiException { + final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context); + return getAccountById(id, internalTenantContext); + } + + @Override public Account createAccount(final AccountData data, final CallContext context) throws AccountApiException { // Not transactional, but there is a db constraint on that column @@ -69,25 +90,6 @@ public Account createAccount(final AccountData data, final CallContext context) return new DefaultAccount(account); } - @Override - public Account getAccountByKey(final String key, final TenantContext context) throws AccountApiException { - final AccountModelDao account = accountDao.getAccountByKey(key, internalCallContextFactory.createInternalTenantContext(context)); - if (account == null) { - throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key); - } - - return new DefaultAccount(account); - } - - @Override - public Account getAccountById(final UUID id, final TenantContext context) throws AccountApiException { - final AccountModelDao account = accountDao.getById(id, internalCallContextFactory.createInternalTenantContext(context)); - if (account == null) { - throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, id); - } - - return new DefaultAccount(account); - } @Override public Pagination searchAccounts(final String searchKey, final Long offset, final Long limit, final TenantContext context) { diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java index 2b79ca2036..ec9b237dc2 100644 --- a/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java +++ b/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java @@ -28,26 +28,28 @@ public interface AccountDao extends EntityDao { - public AccountModelDao getAccountByKey(String key, InternalTenantContext context); + AccountModelDao getAccountByKey(String key, InternalTenantContext context); - public Pagination searchAccounts(String searchKey, Long offset, Long limit, InternalTenantContext context); + Pagination searchAccounts(String searchKey, Long offset, Long limit, InternalTenantContext context); /** * @throws AccountApiException when externalKey is null */ - public UUID getIdFromKey(String externalKey, InternalTenantContext context) throws AccountApiException; + UUID getIdFromKey(String externalKey, InternalTenantContext context) throws AccountApiException; /** * @param accountId the id of the account * @param paymentMethodId the is of the current default paymentMethod */ - public void updatePaymentMethod(UUID accountId, UUID paymentMethodId, InternalCallContext context) throws AccountApiException; + void updatePaymentMethod(UUID accountId, UUID paymentMethodId, InternalCallContext context) throws AccountApiException; - public void update(AccountModelDao account, InternalCallContext context) throws AccountApiException; + void update(AccountModelDao account, InternalCallContext context) throws AccountApiException; - public void addEmail(AccountEmailModelDao email, InternalCallContext context) throws AccountApiException; + void addEmail(AccountEmailModelDao email, InternalCallContext context) throws AccountApiException; - public void removeEmail(AccountEmailModelDao email, InternalCallContext context); + void removeEmail(AccountEmailModelDao email, InternalCallContext context); - public List getEmailsByAccountId(UUID accountId, InternalTenantContext context); + List getEmailsByAccountId(UUID accountId, InternalTenantContext context); + + Long getAccountBCD(UUID accountId, InternalTenantContext context); } diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java index 8eeff93b8b..b566df9506 100644 --- a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java +++ b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java @@ -18,11 +18,6 @@ import java.util.UUID; -import org.skife.jdbi.v2.sqlobject.Bind; -import org.skife.jdbi.v2.sqlobject.BindBean; -import org.skife.jdbi.v2.sqlobject.SqlQuery; -import org.skife.jdbi.v2.sqlobject.SqlUpdate; - import org.killbill.billing.account.api.Account; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalTenantContext; @@ -30,6 +25,10 @@ import org.killbill.billing.util.entity.dao.Audited; import org.killbill.billing.util.entity.dao.EntitySqlDao; import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate; +import org.skife.jdbi.v2.sqlobject.Bind; +import org.skife.jdbi.v2.sqlobject.BindBean; +import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; @EntitySqlDaoStringTemplate public interface AccountSqlDao extends EntitySqlDao { @@ -42,6 +41,10 @@ public AccountModelDao getAccountByKey(@Bind("externalKey") final String key, public UUID getIdFromKey(@Bind("externalKey") final String key, @BindBean final InternalTenantContext context); + @SqlQuery + public Long getBCD(@Bind("id") String accountId, + @BindBean final InternalTenantContext context); + @SqlUpdate @Audited(ChangeType.UPDATE) public void update(@BindBean final AccountModelDao account, diff --git a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java index b14c677a99..79f7b9217a 100644 --- a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java +++ b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java @@ -246,4 +246,13 @@ public List inTransaction(final EntitySqlDaoWrapperFactory }); } + @Override + public Long getAccountBCD(final UUID accountId, final InternalTenantContext context) { + return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() { + @Override + public Long inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception { + return entitySqlDaoWrapperFactory.become(AccountSqlDao.class).getBCD(accountId.toString(), context); + } + }); + } } diff --git a/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg index 0f5bb37dbd..a5000281dc 100644 --- a/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg +++ b/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg @@ -86,6 +86,12 @@ getAccountByKey() ::= << where external_key = :externalKey ; >> +getBCD() ::= << + select billing_cycle_day_local + from accounts + where id = :id ; +>> + searchQuery(prefix) ::= << = :searchKey or name like :likeSearchKey diff --git a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java index 1843b08dd8..f3d7863a55 100644 --- a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java +++ b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java @@ -53,7 +53,7 @@ public class TestDefaultAccountUserApiWithMocks extends AccountTestSuiteNoDB { @BeforeMethod(groups = "fast") public void setUp() throws Exception { accountDao = new MockAccountDao(Mockito.mock(PersistentBus.class)); - accountUserApi = new DefaultAccountUserApi(internalFactory, accountDao); + accountUserApi = new DefaultAccountUserApi(accountDao, nonEntityDao, controllerDispatcher, internalFactory); } @Test(groups = "fast", description = "Test Account create API") diff --git a/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java b/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java index 04a8bbe291..d60ef8265b 100644 --- a/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java +++ b/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java @@ -163,4 +163,10 @@ public boolean apply(final AccountEmailModelDao input) { })); } + @Override + public Long getAccountBCD(final UUID accountId, final InternalTenantContext context) { + final AccountModelDao account = getById(accountId, context); + return account != null ? account.getBillingCycleDayLocal() : 0L; + } + } diff --git a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java index 9a61b19e14..4f7a92fea6 100644 --- a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java +++ b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java @@ -44,8 +44,6 @@ public interface AccountInternalApi { ImmutableAccountData getImmutableAccountDataById(UUID accountId, InternalTenantContext context) throws AccountApiException; - ImmutableAccountData getImmutableAccountDataByKey(String key, InternalTenantContext context) throws AccountApiException; - ImmutableAccountData getImmutableAccountDataByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException; } diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java index a08193912a..c96270946f 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java @@ -243,8 +243,6 @@ public List getPaymentControlPluginNames() { @Inject protected IDBI idbi; - @Inject - protected CacheControllerDispatcher controlCacheDispatcher; @Inject protected TestApiListener busHandler; @@ -273,9 +271,6 @@ public void beforeMethod() throws Exception { //Thread.currentThread().setContextClassLoader(null); log.debug("RESET TEST FRAMEWORK"); - - controlCacheDispatcher.clearAll(); - overdueConfigCache.loadDefaultOverdueConfig((OverdueConfig) null); clock.resetDeltaFromReality(); diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java index 012fa68b27..6bde96a568 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java @@ -85,8 +85,6 @@ public void beforeMethod() throws Exception { log.debug("RESET TEST FRAMEWORK"); - controlCacheDispatcher.clearAll(); - overdueConfigCache.loadDefaultOverdueConfig((OverdueConfig) null); clock.resetDeltaFromReality(); diff --git a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java index 4db4fd8b51..1e71a186b0 100644 --- a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java +++ b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java @@ -20,6 +20,8 @@ import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule; import org.killbill.billing.platform.api.KillbillConfigSource; +import org.killbill.billing.util.glue.CacheModule; +import org.killbill.billing.util.glue.NonEntityDaoModule; public class TestUsageModuleWithEmbeddedDB extends TestUsageModule { @@ -32,5 +34,7 @@ public void configure() { super.configure(); install(new GuicyKillbillTestWithEmbeddedDBModule(configSource)); + install(new CacheModule(configSource)); + install(new NonEntityDaoModule(configSource)); } } diff --git a/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java new file mode 100644 index 0000000000..d88ea696d5 --- /dev/null +++ b/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.util.cache; + +import java.util.UUID; + +import org.killbill.billing.callcontext.InternalTenantContext; +import org.killbill.billing.util.cache.Cachable.CacheType; + +public class AccountBCDCacheLoader extends BaseCacheLoader { + + @Override + public CacheType getCacheType() { + return CacheType.ACCOUNT_BCD; + } + + @Override + public Object load(final Object key, final Object argument) { + + checkCacheLoaderStatus(); + + if (!(key instanceof UUID)) { + throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName()); + } + + if (!(argument instanceof CacheLoaderArgument)) { + throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName()); + } + + final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument; + + if (cacheLoaderArgument.getArgs() == null || + !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) { + throw new IllegalArgumentException("Missing LoaderCallback from the arguments "); + } + + final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0]; + return callback.loadAccountBCD((UUID) key, cacheLoaderArgument.getInternalTenantContext()); + } + + public interface LoaderCallback { + Object loadAccountBCD(final UUID accountId, final InternalTenantContext context); + } +} diff --git a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java index 767cc94cb7..bfc0b2718b 100644 --- a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java +++ b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java @@ -25,20 +25,22 @@ @Target({ElementType.METHOD}) public @interface Cachable { - public final String RECORD_ID_CACHE_NAME = "record-id"; - public final String ACCOUNT_RECORD_ID_CACHE_NAME = "account-record-id"; - public final String TENANT_RECORD_ID_CACHE_NAME = "tenant-record-id"; - public final String OBJECT_ID_CACHE_NAME = "object-id"; - public final String AUDIT_LOG_CACHE_NAME = "audit-log"; - public final String AUDIT_LOG_VIA_HISTORY_CACHE_NAME = "audit-log-via-history"; - public final String TENANT_CATALOG_CACHE_NAME = "tenant-catalog"; - public final String TENANT_OVERDUE_CONFIG_CACHE_NAME = "tenant-overdue-config"; - public final String TENANT_KV_CACHE_NAME = "tenant-kv"; - public final String OVERRIDDEN_PLAN_CACHE_NAME = "overridden-plan"; - - public CacheType value(); - - public enum CacheType { + String RECORD_ID_CACHE_NAME = "record-id"; + String ACCOUNT_RECORD_ID_CACHE_NAME = "account-record-id"; + String TENANT_RECORD_ID_CACHE_NAME = "tenant-record-id"; + String OBJECT_ID_CACHE_NAME = "object-id"; + String AUDIT_LOG_CACHE_NAME = "audit-log"; + String AUDIT_LOG_VIA_HISTORY_CACHE_NAME = "audit-log-via-history"; + String TENANT_CATALOG_CACHE_NAME = "tenant-catalog"; + String TENANT_OVERDUE_CONFIG_CACHE_NAME = "tenant-overdue-config"; + String TENANT_KV_CACHE_NAME = "tenant-kv"; + String OVERRIDDEN_PLAN_CACHE_NAME = "overridden-plan"; + String ACCOUNT_IMMUTABLE_CACHE_NAME = "account-immutable"; + String ACCOUNT_BCD_CACHE_NAME = "account-bcd"; + + CacheType value(); + + enum CacheType { /* Mapping from object 'id (UUID)' -> object 'recordId (Long' */ RECORD_ID(RECORD_ID_CACHE_NAME, false), @@ -68,7 +70,13 @@ public enum CacheType { TENANT_KV(TENANT_KV_CACHE_NAME, false), /* Overwritten plans */ - OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_CACHE_NAME, false); + OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_CACHE_NAME, false), + + /* Immutable account data config cache */ + ACCOUNT_IMMUTABLE(ACCOUNT_IMMUTABLE_CACHE_NAME, false), + + /* Account BCD config cache */ + ACCOUNT_BCD(ACCOUNT_BCD_CACHE_NAME, false); private final String cacheName; private final boolean isKeyPrefixedWithTableName; diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java index 5743f65ca6..2c6be1ed71 100644 --- a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java +++ b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java @@ -20,13 +20,17 @@ public interface CacheController { - public void add(K key, V value); + void add(K key, V value); - public V get(K key, CacheLoaderArgument objectType); + V get(K key, CacheLoaderArgument objectType); - public boolean remove(K key); + V get(K key); - public int size(); + boolean remove(K key); + + void putIfAbsent(final K key, V value); + + int size(); void removeAll(); diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java index 1f7998f469..08d04d203a 100644 --- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java +++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java @@ -18,6 +18,8 @@ package org.killbill.billing.util.cache; +import javax.annotation.Nullable; + import org.killbill.billing.util.cache.Cachable.CacheType; import net.sf.ehcache.Ehcache; @@ -39,12 +41,18 @@ public void add(final K key, final V value) { } @Override - public V get(final K key, final CacheLoaderArgument cacheLoaderArgument) { - final Element element = cache.getWithLoader(key, null, cacheLoaderArgument); - if (element == null || element.getObjectValue() == null || element.getObjectValue().equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) { - return null; - } - return (V) element.getObjectValue(); + public V get(final K key, @Nullable final CacheLoaderArgument cacheLoaderArgument) { + return getWithOrWithoutCacheLoaderArgument(key, cacheLoaderArgument); + } + + @Override + public V get(final K key) { + return getWithOrWithoutCacheLoaderArgument(key, null); + } + + public void putIfAbsent(final K key, V value) { + final Element element = new Element(key, value); + cache.putIfAbsent(element); } @Override @@ -66,4 +74,13 @@ public void removeAll() { public CacheType getCacheType() { return cacheType; } + + private V getWithOrWithoutCacheLoaderArgument(final K key, @Nullable final CacheLoaderArgument cacheLoaderArgument) { + final Element element = cacheLoaderArgument != null ? cache.getWithLoader(key, null, cacheLoaderArgument) : cache.get(key); + if (element == null || element.getObjectValue() == null || element.getObjectValue().equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) { + return null; + } + return (V) element.getObjectValue(); + } + } diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java index 42d89663ae..269ff66df5 100644 --- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java +++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java @@ -51,6 +51,8 @@ public class EhCacheCacheManagerProvider implements Provider { @Inject public EhCacheCacheManagerProvider(final MetricRegistry metricRegistry, final CacheConfig cacheConfig, + final ImmutableAccountCacheLoader accountCacheLoader, + final AccountBCDCacheLoader accountBCDCacheLoader, final RecordIdCacheLoader recordIdCacheLoader, final AccountRecordIdCacheLoader accountRecordIdCacheLoader, final TenantRecordIdCacheLoader tenantRecordIdCacheLoader, @@ -63,6 +65,8 @@ public EhCacheCacheManagerProvider(final MetricRegistry metricRegistry, final OverriddenPlanCacheLoader overriddenPlanCacheLoader) { this.metricRegistry = metricRegistry; this.cacheConfig = cacheConfig; + cacheLoaders.add(accountCacheLoader); + cacheLoaders.add(accountBCDCacheLoader); cacheLoaders.add(recordIdCacheLoader); cacheLoaders.add(accountRecordIdCacheLoader); cacheLoaders.add(tenantRecordIdCacheLoader); diff --git a/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java new file mode 100644 index 0000000000..866c44efd3 --- /dev/null +++ b/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java @@ -0,0 +1,57 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.util.cache; + +import org.killbill.billing.callcontext.InternalTenantContext; +import org.killbill.billing.util.cache.Cachable.CacheType; + +public class ImmutableAccountCacheLoader extends BaseCacheLoader { + + @Override + public CacheType getCacheType() { + return CacheType.ACCOUNT_IMMUTABLE; + } + + @Override + public Object load(final Object key, final Object argument) { + + checkCacheLoaderStatus(); + + if (!(key instanceof Long)) { + throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName()); + } + + if (!(argument instanceof CacheLoaderArgument)) { + throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName()); + } + + final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument; + + if (cacheLoaderArgument.getArgs() == null || + !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) { + throw new IllegalArgumentException("Missing LoaderCallback from the arguments "); + } + + final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0]; + return callback.loadAccount((Long) key, cacheLoaderArgument.getInternalTenantContext()); + } + + public interface LoaderCallback { + Object loadAccount(final Long recordId, final InternalTenantContext context); + } +} diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java index 6a1accf450..897e75d4e9 100644 --- a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java +++ b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java @@ -81,7 +81,6 @@ public Object load(final Object key, final Object argument) { } public interface LoaderCallback { - public Object loadCatalog(final List catalogXMLs, final Long tenantRecordId) throws CatalogApiException; } } diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml index 7769245f54..d172871328 100644 --- a/util/src/main/resources/ehcache.xml +++ b/util/src/main/resources/ehcache.xml @@ -168,6 +168,32 @@ properties=""/> + + + + + + + + diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java index eac77a7438..565027ed29 100644 --- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java +++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java @@ -19,6 +19,7 @@ import javax.inject.Inject; import javax.sql.DataSource; +import org.killbill.billing.util.cache.CacheControllerDispatcher; import org.killbill.commons.embeddeddb.EmbeddedDB; import org.skife.jdbi.v2.IDBI; import org.slf4j.Logger; @@ -40,6 +41,10 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite @Inject protected IDBI dbi; + @Inject + protected CacheControllerDispatcher controlCacheDispatcher; + + @BeforeSuite(groups = "slow") public void beforeSuite() throws Exception { DBTestingHelper.get().start(); @@ -51,6 +56,7 @@ public void beforeMethod() throws Exception { DBTestingHelper.get().getInstance().cleanupAllTables(); } catch (final Exception ignored) { } + controlCacheDispatcher.clearAll(); } @AfterSuite(groups = "slow") diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java index caff3f8d54..df64546b34 100644 --- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java +++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java @@ -59,8 +59,6 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite @Inject protected PersistentBus eventBus; @Inject - protected CacheControllerDispatcher controlCacheDispatcher; - @Inject protected NonEntityDao nonEntityDao; @Inject protected InternalCallContextFactory internalCallContextFactory; @@ -112,8 +110,6 @@ public void beforeMethod() throws Exception { eventBus.start(); eventBus.register(eventsListener); - controlCacheDispatcher.clearAll(); - // Make sure we start with a clean state assertListenerStatus(); } From ad41bf184e7114fdfe32ce71a73f91e2bba65a03 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 2 Oct 2015 14:56:58 -0700 Subject: [PATCH 104/137] Code review integration for 86379933aa486c9e81cef2ab3113a68d2dced360 Throw an exception in updateBCD if the BCD is already set Simplify BCD update (Long v.s int) for consistency) --- .../account/api/svcs/DefaultAccountInternalApi.java | 7 +++++-- .../java/org/killbill/billing/account/dao/AccountDao.java | 2 +- .../org/killbill/billing/account/dao/AccountSqlDao.java | 2 +- .../killbill/billing/account/dao/DefaultAccountDao.java | 6 +++--- .../org/killbill/billing/account/dao/MockAccountDao.java | 4 ++-- .../plumbing/billing/DefaultInternalBillingApi.java | 4 ++++ 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java index 41791b62c1..de00e98cc6 100644 --- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java +++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java @@ -92,6 +92,9 @@ public void updateBCD(final String externalKey, final int bcd, if (currentAccount == null) { throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey); } + if (currentAccount.getBillCycleDayLocal() != DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL) { + throw new AccountApiException(ErrorCode.ACCOUNT_UPDATE_FAILED); + } final MutableAccountData mutableAccountData = currentAccount.toMutableAccountData(); mutableAccountData.setBillCycleDayLocal(bcd); @@ -157,8 +160,8 @@ private AccountModelDao getAccountModelDaoByRecordId(final Long recordId, final } private int getBCDInternal(final UUID accountId, final InternalTenantContext context) { - final Long bcd = accountDao.getAccountBCD(accountId, context); - return bcd != null ? bcd.intValue() : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL; + final Integer bcd = accountDao.getAccountBCD(accountId, context); + return bcd != null ? bcd : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL; } private CacheLoaderArgument createImmutableAccountCacheLoaderArgument(final InternalTenantContext context) { diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java index ec9b237dc2..c5cae6d18d 100644 --- a/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java +++ b/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java @@ -51,5 +51,5 @@ public interface AccountDao extends EntityDao getEmailsByAccountId(UUID accountId, InternalTenantContext context); - Long getAccountBCD(UUID accountId, InternalTenantContext context); + Integer getAccountBCD(UUID accountId, InternalTenantContext context); } diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java index b566df9506..5395c85fa4 100644 --- a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java +++ b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java @@ -42,7 +42,7 @@ public UUID getIdFromKey(@Bind("externalKey") final String key, @BindBean final InternalTenantContext context); @SqlQuery - public Long getBCD(@Bind("id") String accountId, + public Integer getBCD(@Bind("id") String accountId, @BindBean final InternalTenantContext context); @SqlUpdate diff --git a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java index 79f7b9217a..31c33e3ac2 100644 --- a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java +++ b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java @@ -247,10 +247,10 @@ public List inTransaction(final EntitySqlDaoWrapperFactory } @Override - public Long getAccountBCD(final UUID accountId, final InternalTenantContext context) { - return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() { + public Integer getAccountBCD(final UUID accountId, final InternalTenantContext context) { + return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() { @Override - public Long inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception { + public Integer inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception { return entitySqlDaoWrapperFactory.become(AccountSqlDao.class).getBCD(accountId.toString(), context); } }); diff --git a/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java b/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java index d60ef8265b..e9715ddd5a 100644 --- a/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java +++ b/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java @@ -164,9 +164,9 @@ public boolean apply(final AccountEmailModelDao input) { } @Override - public Long getAccountBCD(final UUID accountId, final InternalTenantContext context) { + public Integer getAccountBCD(final UUID accountId, final InternalTenantContext context) { final AccountModelDao account = getById(accountId, context); - return account != null ? account.getBillingCycleDayLocal() : 0L; + return account != null ? account.getBillingCycleDayLocal() : 0; } } diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java index be015309c5..3fbf992a59 100644 --- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java +++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java @@ -25,6 +25,7 @@ import org.killbill.billing.ObjectType; import org.killbill.billing.account.api.AccountApiException; import org.killbill.billing.account.api.AccountInternalApi; +import org.killbill.billing.account.api.AccountUserApi; import org.killbill.billing.account.api.ImmutableAccountData; import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.catalog.api.CatalogApiException; @@ -191,6 +192,9 @@ private void addBillingEventsForSubscription(final ImmutableAccountData account, } catch (CatalogApiException e) { log.error("Failing to identify catalog components while creating BillingEvent from transition: " + transition.getId().toString(), e); + } catch (AccountApiException e) { + // This is unexpected (failed to update BCD) but if this happens we don't want to ignore.. + throw e; } catch (Exception e) { log.warn("Failed while getting BillingEvent", e); } From 01dbd19db68ef55d7b662869e41708aef208a5dd Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sun, 4 Oct 2015 12:24:37 -0700 Subject: [PATCH 105/137] account: Fix regression introduced in 86379933aa486c9e81cef2ab3113a68d2dced360 (where BCD cache needs to be reset fro when we update the value) --- .../billing/account/api/svcs/DefaultAccountInternalApi.java | 1 + 1 file changed, 1 insertion(+) diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java index de00e98cc6..0903c13545 100644 --- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java +++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java @@ -99,6 +99,7 @@ public void updateBCD(final String externalKey, final int bcd, final MutableAccountData mutableAccountData = currentAccount.toMutableAccountData(); mutableAccountData.setBillCycleDayLocal(bcd); final AccountModelDao accountToUpdate = new AccountModelDao(currentAccount.getId(), mutableAccountData); + bcdCacheController.remove(currentAccount.getId()); bcdCacheController.putIfAbsent(currentAccount.getId(), new Integer(bcd)); accountDao.update(accountToUpdate, context); } From 879d013a791910253a72cb6ced7387d2337a532d Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Sun, 4 Oct 2015 17:01:33 -0700 Subject: [PATCH 106/137] invoice: Modify dryRun api to allow queriying for upcoming invoices that are either SUBSCRIPTION or BUNDLE aligned. Fixes #401 --- .../beatrix/integration/TestIntegration.java | 15 ++-- .../integration/TestIntegrationBase.java | 16 ++++- .../integration/TestIntegrationInvoice.java | 19 ++++-- .../beatrix/integration/TestSubscription.java | 3 +- .../billing/invoice/InvoiceDispatcher.java | 60 ++++++++++++---- .../billing/invoice/TestInvoiceHelper.java | 8 ++- .../billing/jaxrs/json/InvoiceDryRunJson.java | 13 +++- .../jaxrs/resources/InvoiceResource.java | 68 ++++++++++--------- .../jaxrs/resources/JaxrsResource.java | 2 - .../killbill/billing/jaxrs/TestInvoice.java | 16 ++--- 10 files changed, 148 insertions(+), 72 deletions(-) diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java index 442f5f4c73..98c0ce59ae 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java @@ -40,6 +40,7 @@ import org.killbill.billing.entitlement.api.Entitlement.EntitlementState; import org.killbill.billing.entitlement.api.SubscriptionBundle; import org.killbill.billing.entitlement.api.SubscriptionEventType; +import org.killbill.billing.invoice.api.DryRunType; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItemType; @@ -75,7 +76,7 @@ public void testCancelBPWithAOTheSameDay() throws Exception { // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE // - TestDryRunArguments dryRun = new TestDryRunArguments("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, + TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.START_BILLING, null, null, clock.getUTCNow(), null); Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext); expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0"))); @@ -90,7 +91,7 @@ public void testCancelBPWithAOTheSameDay() throws Exception { // // ADD ADD_ON ON THE SAME DAY // - dryRun = new TestDryRunArguments("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, null, null, + dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.START_BILLING, null, bpSubscription.getBundleId(), clock.getUTCNow(), null); dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext); expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("399.95"))); @@ -106,7 +107,7 @@ public void testCancelBPWithAOTheSameDay() throws Exception { // CANCEL BP ON THE SAME DAY (we should have two cancellations, BP and AO) // There is no invoice created as we only adjust the previous invoice. // - dryRun = new TestDryRunArguments(null, null, null, null, null, SubscriptionEventType.STOP_BILLING, bpSubscription.getId(), + dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, null, null, null, null, null, SubscriptionEventType.STOP_BILLING, bpSubscription.getId(), bpSubscription.getBundleId(), clock.getUTCNow(), null); dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext); expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-399.95"))); @@ -153,7 +154,7 @@ public void testBasePlanCompleteWithBillingDayInPast() throws Exception { // // CHANGE PLAN IMMEDIATELY AND EXPECT BOTH EVENTS: NextEvent.CHANGE NextEvent.INVOICE // - TestDryRunArguments dryRun = new TestDryRunArguments("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE, + TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE, subscription.getId(), subscription.getBundleId(), clock.getUTCNow(), null); Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext); expectedInvoices.add(new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0"))); @@ -173,7 +174,7 @@ public void testBasePlanCompleteWithBillingDayInPast() throws Exception { setDateAndCheckForCompletion(new DateTime(2012, 3, 1, 23, 59, 59, 0, testTimeZone)); DateTime nextDate = clock.getUTCNow().plusDays(1); - dryRun = new TestDryRunArguments(); + dryRun = new TestDryRunArguments(DryRunType.TARGET_DATE); expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2), new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("561.24"))); @@ -202,7 +203,7 @@ public void testBasePlanCompleteWithBillingDayInPast() throws Exception { nextDate = clock.getUTCNow().plusDays(31); - dryRun = new TestDryRunArguments(); + dryRun = new TestDryRunArguments(DryRunType.TARGET_DATE); dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(nextDate, testTimeZone), dryRun, callContext); expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 31), new LocalDate(2012, 4, 30), InvoiceItemType.RECURRING, new BigDecimal("29.95"))); @@ -287,7 +288,7 @@ public void testBasePlanCompleteWithBillingDayAlignedWithTrial() throws Exceptio // - TestDryRunArguments dryRun = new TestDryRunArguments("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE, + TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE, subscription.getId(), subscription.getBundleId(), null, null); try { invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext); diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java index c96270946f..3a17e84add 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java @@ -61,6 +61,7 @@ import org.killbill.billing.entitlement.api.SubscriptionApi; import org.killbill.billing.entitlement.api.SubscriptionEventType; import org.killbill.billing.invoice.api.DryRunArguments; +import org.killbill.billing.invoice.api.DryRunType; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoicePaymentApi; @@ -710,6 +711,7 @@ private T doCallAndCheckForCompletion(final Function f, final NextE protected static class TestDryRunArguments implements DryRunArguments { + private final DryRunType dryRunType; private final PlanPhaseSpecifier spec; private final SubscriptionEventType action; private final UUID subscriptionId; @@ -717,7 +719,8 @@ protected static class TestDryRunArguments implements DryRunArguments { private final DateTime effectiveDate; private final BillingActionPolicy billingPolicy; - public TestDryRunArguments() { + public TestDryRunArguments(final DryRunType dryRunType) { + this.dryRunType = dryRunType; this.spec = null; this.action = null; this.subscriptionId = null; @@ -726,7 +729,8 @@ public TestDryRunArguments() { this.billingPolicy = null; } - public TestDryRunArguments(final String productName, + public TestDryRunArguments(final DryRunType dryRunType, + final String productName, final ProductCategory category, final BillingPeriod billingPeriod, final String priceList, @@ -736,6 +740,7 @@ public TestDryRunArguments(final String productName, final UUID bundleId, final DateTime effectiveDate, final BillingActionPolicy billingPolicy) { + this.dryRunType = dryRunType; this.spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceList, phaseType); this.action = action; this.subscriptionId = subscriptionId; @@ -744,6 +749,11 @@ public TestDryRunArguments(final String productName, this.billingPolicy = billingPolicy; } + @Override + public DryRunType getDryRunType() { + return dryRunType; + } + @Override public PlanPhaseSpecifier getPlanPhaseSpecifier() { return spec; @@ -775,7 +785,7 @@ public BillingActionPolicy getBillingActionPolicy() { } @Override - public List getPlanPhasePriceoverrides() { + public List getPlanPhasePriceOverrides() { return null; } } diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java index 362c1c5bac..9e8941ca36 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java @@ -33,6 +33,7 @@ import org.killbill.billing.catalog.api.ProductCategory; import org.killbill.billing.entitlement.api.DefaultEntitlement; import org.killbill.billing.invoice.api.DryRunArguments; +import org.killbill.billing.invoice.api.DryRunType; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.payment.api.Payment; @@ -78,7 +79,7 @@ public void testDryRunWithNoTargetDate() throws Exception { expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 6, 14), new LocalDate(2015, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95"))); // This will verify that the upcoming Phase is found and the invoice is generated at the right date, with correct items - DryRunArguments dryRun = new TestDryRunArguments(); + DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE); Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext); invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices); @@ -144,12 +145,12 @@ public void testDryRunWithNoTargetDateAndMultipleNonAlignedSubscriptions() throw final List expectedInvoices = new ArrayList(); expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2014, 2, 1), new LocalDate(2015, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95"))); - DryRunArguments dryRun = new TestDryRunArguments(); + DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE); Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext); invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices); busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT); - // 2014-1-2 + // 2014-2-1 clock.addDays(30); assertListenerStatus(); invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices); @@ -184,7 +185,17 @@ public void testDryRunWithNoTargetDateAndMultipleNonAlignedSubscriptions() throw invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices); expectedInvoices.clear(); - // + + // We test first the next expected invoice for a specific subscription: We can see the targetDate is 2015-2-14 and not 2015-2-1 + final DryRunArguments dryRunWIthSubscription = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly.getId(), null, null, null); + expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95"))); + expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95"))); + dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunWIthSubscription, callContext); + assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14)); + invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices); + expectedInvoices.clear(); + + // Then we test first the next expected invoice at the account level expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95"))); dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext); invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices); diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java index 3dc6b0eb15..0d21887eec 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java @@ -24,6 +24,7 @@ import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; import org.killbill.billing.entitlement.api.SubscriptionEventType; +import org.killbill.billing.invoice.api.DryRunType; import org.testng.annotations.Test; import org.killbill.billing.account.api.Account; @@ -87,7 +88,7 @@ public void testForcePolicy() throws Exception { new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2013, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2334.20")), new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("2164.88"), false /* Issue with test where created date for context is wrong*/)); - TestDryRunArguments dryRun = new TestDryRunArguments(productName, ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, + TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, productName, ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE, bpEntitlement.getId(), bpEntitlement.getBundleId(), null, BillingActionPolicy.IMMEDIATE); Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext); invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, toBeChecked); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java index aa5198e2f2..7764c4a09e 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java @@ -53,6 +53,7 @@ import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification; import org.killbill.billing.invoice.api.DefaultInvoiceService; import org.killbill.billing.invoice.api.DryRunArguments; +import org.killbill.billing.invoice.api.DryRunType; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItem; @@ -75,6 +76,7 @@ import org.killbill.billing.invoice.model.RecurringInvoiceItem; import org.killbill.billing.invoice.notification.DefaultNextBillingDateNotifier; import org.killbill.billing.invoice.notification.NextBillingDateNotificationKey; +import org.killbill.billing.junction.BillingEvent; import org.killbill.billing.junction.BillingEventSet; import org.killbill.billing.junction.BillingInternalApi; import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi; @@ -115,7 +117,7 @@ public class InvoiceDispatcher { private static final Ordering UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural(); private final static Joiner JOINER_COMMA = Joiner.on(","); - private static final NullDryRunArguments NULL_DRY_RUN_ARGUMENTS = new NullDryRunArguments(); + private static final TargetDateDryRunArguments TARGET_DATE_DRY_RUN_ARGUMENTS = new TargetDateDryRunArguments(); private final InvoiceGenerator generator; private final BillingInternalApi billingApi; @@ -191,7 +193,7 @@ private Invoice processSubscriptionInternal(final UUID subscriptionId, final Dat return null; } final UUID accountId = subscriptionApi.getAccountIdFromSubscriptionId(subscriptionId, context); - final DryRunArguments dryRunArguments = dryRunForNotification ? NULL_DRY_RUN_ARGUMENTS : null; + final DryRunArguments dryRunArguments = dryRunForNotification ? TARGET_DATE_DRY_RUN_ARGUMENTS : null; return processAccount(accountId, targetDate, dryRunArguments, context); } catch (final SubscriptionBaseApiException e) { @@ -225,14 +227,17 @@ private Invoice processAccountWithLock(final UUID accountId, @Nullable final Dat final boolean isDryRun = dryRunArguments != null; // A null inputTargetDateTime is only allowed in dryRun mode to have the system compute it - Preconditions.checkArgument(inputTargetDateTime != null || isDryRun, "inputTargetDateTime is required in non dryRun mode"); + Preconditions.checkArgument(inputTargetDateTime != null || + (dryRunArguments != null && DryRunType.UPCOMING_INVOICE.equals(dryRunArguments.getDryRunType())), "inputTargetDateTime is required in non dryRun mode"); try { // Make sure to first set the BCD if needed then get the account object (to have the BCD set) final BillingEventSet billingEvents = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId, dryRunArguments, context); if (billingEvents.isEmpty()) { return null; } - final List candidateDateTimes = (inputTargetDateTime != null) ? ImmutableList.of(inputTargetDateTime) : getUpcomingInvoiceCandidateDates(context); + final List candidateDateTimes = (inputTargetDateTime != null) ? + ImmutableList.of(inputTargetDateTime) : + getUpcomingInvoiceCandidateDates(getFilteredSubscriptionIds(dryRunArguments, billingEvents), context); for (final DateTime curTargetDateTime : candidateDateTimes) { final Invoice invoice = processAccountWithLockAndInputTargetDate(accountId, curTargetDateTime, billingEvents, isDryRun, context); if (invoice != null) { @@ -246,6 +251,30 @@ private Invoice processAccountWithLock(final UUID accountId, @Nullable final Dat } } + + private Iterable getFilteredSubscriptionIds(final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) { + if (!dryRunArguments.getDryRunType().equals(DryRunType.UPCOMING_INVOICE) || + (dryRunArguments.getSubscriptionId() == null && dryRunArguments.getBundleId() == null)) { + return ImmutableList.of(); + } + + if (dryRunArguments.getSubscriptionId() != null) { + return ImmutableList.of(dryRunArguments.getSubscriptionId()); + } + + return Iterables.transform(Iterables.filter(billingEvents, new Predicate() { + @Override + public boolean apply(final BillingEvent input) { + return input.getSubscription().getBundleId().equals(dryRunArguments.getBundleId()); + } + }), new Function() { + @Override + public UUID apply(final BillingEvent input) { + return input.getSubscription().getId(); + } + }); + } + private Invoice processAccountWithLockAndInputTargetDate(final UUID accountId, final DateTime targetDateTime, final BillingEventSet billingEvents, final boolean isDryRun, final InternalCallContext context) throws InvoiceApiException { try { @@ -572,30 +601,31 @@ public boolean isForInvoiceNotificationTrigger() { } } - private List getUpcomingInvoiceCandidateDates(final InternalCallContext internalCallContext) { - final Iterable nextScheduledInvoiceDates = getNextScheduledInvoiceEffectiveDate(internalCallContext); + private List getUpcomingInvoiceCandidateDates(final Iterable filteredSubscriptionIds, final InternalCallContext internalCallContext) { + final Iterable nextScheduledInvoiceDates = getNextScheduledInvoiceEffectiveDate(filteredSubscriptionIds, internalCallContext); final Iterable nextScheduledSubscriptionsEventDates = subscriptionApi.getFutureNotificationsForAccount(internalCallContext); Iterables.concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEventDates); return UPCOMING_NOTIFICATION_DATE_ORDERING.sortedCopy(Iterables.concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEventDates)); } - private Iterable getNextScheduledInvoiceEffectiveDate(final InternalCallContext internalCallContext) { + private Iterable getNextScheduledInvoiceEffectiveDate(final Iterable filteredSubscriptionIds, final InternalCallContext internalCallContext) { try { final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME, DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE); final List> futureNotifications = notificationQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()); - final Iterable> filtered = Iterables.filter(futureNotifications, new Predicate>() { + final Iterable> allUpcomingEvents = Iterables.filter(futureNotifications, new Predicate>() { @Override public boolean apply(@Nullable final NotificationEventWithMetadata input) { + final boolean isEventForSubscription = !filteredSubscriptionIds.iterator().hasNext() || Iterables.contains(filteredSubscriptionIds, input.getEvent().getUuidKey()); final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ? input.getEvent().isDryRunForInvoiceNotification() : false; - return !isEventDryRunForNotifications; + return isEventForSubscription && !isEventDryRunForNotifications; } }); - return Iterables.transform(filtered, new Function, DateTime>() { + return Iterables.transform(allUpcomingEvents, new Function, DateTime>() { @Nullable @Override public DateTime apply(@Nullable final NotificationEventWithMetadata input) { @@ -607,7 +637,12 @@ public DateTime apply(@Nullable final NotificationEventWithMetadata getPlanPhasePriceoverrides() { + public List getPlanPhasePriceOverrides() { return null; } } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java index 20a900729c..0ff2d289b7 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java @@ -52,6 +52,7 @@ import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications; import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification; import org.killbill.billing.invoice.api.DryRunArguments; +import org.killbill.billing.invoice.api.DryRunType; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItem; @@ -421,6 +422,11 @@ public static class DryRunFutureDateArguments implements DryRunArguments { public DryRunFutureDateArguments() { } + @Override + public DryRunType getDryRunType() { + return DryRunType.TARGET_DATE; + } + @Override public PlanPhaseSpecifier getPlanPhaseSpecifier() { return null; @@ -452,7 +458,7 @@ public BillingActionPolicy getBillingActionPolicy() { } @Override - public List getPlanPhasePriceoverrides() { + public List getPlanPhasePriceOverrides() { return null; } } diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java index 9bc33acad6..bc82ab30b8 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java @@ -28,6 +28,7 @@ public class InvoiceDryRunJson { + private final String dryRunType; private final String dryRunAction; private final String phaseType; private final String productName; @@ -41,7 +42,8 @@ public class InvoiceDryRunJson { private final List priceOverrides; @JsonCreator - public InvoiceDryRunJson(@JsonProperty("dryRunAction") @Nullable final String dryRunAction, + public InvoiceDryRunJson(@JsonProperty("dryRunType") @Nullable final String dryRunType, + @JsonProperty("dryRunAction") @Nullable final String dryRunAction, @JsonProperty("phaseType") @Nullable final String phaseType, @JsonProperty("productName") @Nullable final String productName, @JsonProperty("productCategory") @Nullable final String productCategory, @@ -52,6 +54,7 @@ public InvoiceDryRunJson(@JsonProperty("dryRunAction") @Nullable final String dr @JsonProperty("effectiveDate") @Nullable final LocalDate effectiveDate, @JsonProperty("billingPolicy") @Nullable final String billingPolicy, @JsonProperty("priceOverrides") @Nullable final List priceOverrides) { + this.dryRunType = dryRunType; this.dryRunAction = dryRunAction; this.phaseType = phaseType; this.productName = productName; @@ -65,6 +68,10 @@ public InvoiceDryRunJson(@JsonProperty("dryRunAction") @Nullable final String dr this.priceOverrides = priceOverrides; } + public String getDryRunType() { + return dryRunType; + } + public String getDryRunAction() { return dryRunAction; } @@ -129,6 +136,9 @@ public boolean equals(final Object o) { if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) { return false; } + if (dryRunType != null ? !dryRunType.equals(that.dryRunType) : that.dryRunType != null) { + return false; + } if (dryRunAction != null ? !dryRunAction.equals(that.dryRunAction) : that.dryRunAction != null) { return false; } @@ -160,6 +170,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { int result = dryRunAction != null ? dryRunAction.hashCode() : 0; + result = 31 * result + (dryRunType != null ? dryRunType.hashCode() : 0); result = 31 * result + (phaseType != null ? phaseType.hashCode() : 0); result = 31 * result + (productName != null ? productName.hashCode() : 0); result = 31 * result + (productCategory != null ? productCategory.hashCode() : 0); diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java index 180a800486..7b82b75cba 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java @@ -70,6 +70,7 @@ import org.killbill.billing.entitlement.api.SubscriptionApiException; import org.killbill.billing.entitlement.api.SubscriptionEventType; import org.killbill.billing.invoice.api.DryRunArguments; +import org.killbill.billing.invoice.api.DryRunType; import org.killbill.billing.invoice.api.Invoice; import org.killbill.billing.invoice.api.InvoiceApiException; import org.killbill.billing.invoice.api.InvoiceItem; @@ -146,7 +147,6 @@ public int compare(final InvoicePaymentJson o1, final InvoicePaymentJson o2) { } }); - @Inject public InvoiceResource(final AccountUserApi accountUserApi, final InvoiceUserApi invoiceApi, @@ -315,7 +315,6 @@ public Response createFutureInvoice(@QueryParam(QUERY_ACCOUNT_ID) final String a } } - @Timed @POST @Path("/" + DRY_RUN) @@ -333,14 +332,14 @@ public Response generateDryRunInvoice(@Nullable final InvoiceDryRunJson dryRunSu @javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException, InvoiceApiException { final CallContext callContext = context.createContext(createdBy, reason, comment, request); final LocalDate inputDate; - // In the case of subscription dryRun we set the targetDate to be the effective date of the change itself - if (dryRunSubscriptionSpec != null && dryRunSubscriptionSpec.getEffectiveDate() != null) { - inputDate = dryRunSubscriptionSpec.getEffectiveDate(); - // In case of Invoice dryRun we also allow the special value UPCOMING_INVOICE_TARGET_DATE where the system will automatically - // generate the resulting targetDate for upcoming invoice; in terms of invoice api that maps to passing a null targetDate - } else if (targetDate != null && targetDate.equals(UPCOMING_INVOICE_TARGET_DATE)) { - inputDate = null; - // Finally, in case of Invoice dryRun, we allow a null input date (will default to NOW), or extract the value provided + if (dryRunSubscriptionSpec != null) { + if (DryRunType.UPCOMING_INVOICE.name().equals(dryRunSubscriptionSpec.getDryRunType())) { + inputDate = null; + } else if (DryRunType.SUBSCRIPTION_ACTION.name().equals(dryRunSubscriptionSpec.getDryRunType()) && dryRunSubscriptionSpec.getEffectiveDate() != null) { + inputDate = dryRunSubscriptionSpec.getEffectiveDate(); + } else { + inputDate = toLocalDate(UUID.fromString(accountId), targetDate, callContext); + } } else { inputDate = toLocalDate(UUID.fromString(accountId), targetDate, callContext); } @@ -540,7 +539,6 @@ public InvoiceItemJson apply(final InvoiceItemJson input) { } } - @Timed @GET @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS) @@ -944,6 +942,7 @@ protected ObjectType getObjectType() { private static class DefaultDryRunArguments implements DryRunArguments { + private final DryRunType dryRunType; private final SubscriptionEventType action; private final UUID subscriptionId; private final DateTime effectiveDate; @@ -954,6 +953,7 @@ private static class DefaultDryRunArguments implements DryRunArguments { public DefaultDryRunArguments(final InvoiceDryRunJson input, final DateTimeZone accountTimeZone, final Currency currency, final Clock clock) { if (input == null) { + this.dryRunType = DryRunType.TARGET_DATE; this.action = null; this.subscriptionId = null; this.effectiveDate = null; @@ -962,36 +962,42 @@ public DefaultDryRunArguments(final InvoiceDryRunJson input, final DateTimeZone this.billingPolicy = null; this.overrides = null; } else { + this.dryRunType = input.getDryRunType() != null ? DryRunType.valueOf(input.getDryRunType()) : DryRunType.TARGET_DATE; this.action = input.getDryRunAction() != null ? SubscriptionEventType.valueOf(input.getDryRunAction()) : null; this.subscriptionId = input.getSubscriptionId() != null ? UUID.fromString(input.getSubscriptionId()) : null; this.bundleId = input.getBundleId() != null ? UUID.fromString(input.getBundleId()) : null; this.effectiveDate = input.getEffectiveDate() != null ? ClockUtil.computeDateTimeWithUTCReferenceTime(input.getEffectiveDate(), clock.getUTCNow().toLocalTime(), accountTimeZone, clock) : null; this.billingPolicy = input.getBillingPolicy() != null ? BillingActionPolicy.valueOf(input.getBillingPolicy()) : null; - final PlanPhaseSpecifier planPhaseSpecifier = (input.getProductName() != null && - input.getProductCategory() != null && - input.getBillingPeriod() != null) ? - new PlanPhaseSpecifier(input.getProductName(), - ProductCategory.valueOf(input.getProductCategory()), - BillingPeriod.valueOf(input.getBillingPeriod()), - input.getPriceListName(), - input.getPhaseType() != null ? PhaseType.valueOf(input.getPhaseType()) : null) : - null; + final PlanPhaseSpecifier planPhaseSpecifier = (input.getProductName() != null && + input.getProductCategory() != null && + input.getBillingPeriod() != null) ? + new PlanPhaseSpecifier(input.getProductName(), + ProductCategory.valueOf(input.getProductCategory()), + BillingPeriod.valueOf(input.getBillingPeriod()), + input.getPriceListName(), + input.getPhaseType() != null ? PhaseType.valueOf(input.getPhaseType()) : null) : + null; this.specifier = planPhaseSpecifier; this.overrides = input.getPriceOverrides() != null ? ImmutableList.copyOf(Iterables.transform(input.getPriceOverrides(), new Function() { - @Nullable - @Override - public PlanPhasePriceOverride apply(@Nullable final PhasePriceOverrideJson input) { - if (input.getPhaseName() != null) { - return new DefaultPlanPhasePriceOverride(input.getPhaseName(), currency, input.getFixedPrice(), input.getRecurringPrice()); - } else { - return new DefaultPlanPhasePriceOverride(planPhaseSpecifier, currency, input.getFixedPrice(), input.getRecurringPrice()); - } - } - })) : ImmutableList.of(); + @Nullable + @Override + public PlanPhasePriceOverride apply(@Nullable final PhasePriceOverrideJson input) { + if (input.getPhaseName() != null) { + return new DefaultPlanPhasePriceOverride(input.getPhaseName(), currency, input.getFixedPrice(), input.getRecurringPrice()); + } else { + return new DefaultPlanPhasePriceOverride(planPhaseSpecifier, currency, input.getFixedPrice(), input.getRecurringPrice()); + } + } + })) : ImmutableList.of(); } } + @Override + public DryRunType getDryRunType() { + return dryRunType; + } + @Override public PlanPhaseSpecifier getPlanPhaseSpecifier() { return specifier; @@ -1023,7 +1029,7 @@ public BillingActionPolicy getBillingActionPolicy() { } @Override - public List getPlanPhasePriceoverrides() { + public List getPlanPhasePriceOverrides() { return overrides; } } diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java index 7a97f65dd5..0735138d90 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java @@ -227,8 +227,6 @@ public interface JaxrsResource { public static final String INVOICE_TRANSLATION = "translation"; public static final String INVOICE_CATALOG_TRANSLATION = "catalogTranslation"; - public static final String UPCOMING_INVOICE_TARGET_DATE = "upcomingInvoiceTargetDate"; - public static final String COMBO = "combo"; } diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java index 0c65c04015..4457006647 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java @@ -24,8 +24,6 @@ import java.util.List; import java.util.UUID; -import javax.annotation.Nullable; - import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; @@ -40,9 +38,9 @@ import org.killbill.billing.client.model.InvoicePayment; import org.killbill.billing.client.model.InvoicePayments; import org.killbill.billing.client.model.Invoices; -import org.killbill.billing.client.model.Payment; import org.killbill.billing.client.model.PaymentMethod; import org.killbill.billing.entitlement.api.SubscriptionEventType; +import org.killbill.billing.invoice.api.DryRunType; import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin; import org.killbill.billing.util.api.AuditLevel; import org.testng.Assert; @@ -52,7 +50,6 @@ import com.google.common.collect.Iterables; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @@ -95,7 +92,10 @@ public void testInvoiceOk() throws Exception { assertEquals(firstInvoiceByNumberJson, invoiceJson); // Then create a dryRun for next upcoming invoice - final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), null, true, null, createdBy, reason, comment); + final InvoiceDryRun dryRunArg = new InvoiceDryRun(DryRunType.UPCOMING_INVOICE, null, + null, null, null, null, null, null, null, null, null, null); + + final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), null, dryRunArg, createdBy, reason, comment); assertEquals(dryRunInvoice.getBalance(), new BigDecimal("249.95")); assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2012, 6, 25)); assertEquals(dryRunInvoice.getItems().size(), 1); @@ -112,7 +112,6 @@ public void testInvoiceOk() throws Exception { assertEquals(newInvoiceList.size(), 3); } - @Test(groups = "slow", description = "Can create a subscription in dryRun mode and get an invoice back") public void testDryRunSubscriptionCreate() throws Exception { final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0); @@ -120,9 +119,9 @@ public void testDryRunSubscriptionCreate() throws Exception { // "Assault-Rifle", BillingPeriod.ANNUAL, "rescue", BillingActionPolicy.IMMEDIATE, final Account accountJson = createAccountWithDefaultPaymentMethod(); - final InvoiceDryRun dryRunArg = new InvoiceDryRun(SubscriptionEventType.START_BILLING, + final InvoiceDryRun dryRunArg = new InvoiceDryRun(DryRunType.TARGET_DATE, SubscriptionEventType.START_BILLING, null, "Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, null, null, null, null, null, null); - final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), new LocalDate(initialDate, DateTimeZone.forID(accountJson.getTimeZone())), false, dryRunArg, createdBy, reason, comment); + final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), new LocalDate(initialDate, DateTimeZone.forID(accountJson.getTimeZone())), dryRunArg, createdBy, reason, comment); assertEquals(dryRunInvoice.getItems().size(), 1); } @@ -352,7 +351,6 @@ public void testExternalChargeOnNewInvoice() throws Exception { assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId()).size(), 3); } - @Test(groups = "slow", description = "Can create multiple external charges") public void testExternalCharges() throws Exception { final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice(); From 15d0e363d65e46d8814350e0860573abb790862d Mon Sep 17 00:00:00 2001 From: Matias Aguero Date: Mon, 5 Oct 2015 11:41:06 -0300 Subject: [PATCH 107/137] issues:396 - UTC was set as default account timezone. --- .../org/killbill/billing/account/dao/AccountModelDao.java | 2 +- .../src/main/resources/org/killbill/billing/account/ddl.sql | 4 ++-- .../java/org/killbill/billing/account/dao/TestAccountDao.java | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java index 353812eaaa..ac703624f2 100644 --- a/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java +++ b/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java @@ -74,7 +74,7 @@ public AccountModelDao(final UUID id, final DateTime createdDate, final DateTime this.currency = currency; this.billingCycleDayLocal = billingCycleDayLocal; this.paymentMethodId = paymentMethodId; - this.timeZone = timeZone; + this.timeZone = MoreObjects.firstNonNull(timeZone, DateTimeZone.UTC); this.locale = locale; this.address1 = address1; this.address2 = address2; diff --git a/account/src/main/resources/org/killbill/billing/account/ddl.sql b/account/src/main/resources/org/killbill/billing/account/ddl.sql index 50227ce515..01158e4a77 100644 --- a/account/src/main/resources/org/killbill/billing/account/ddl.sql +++ b/account/src/main/resources/org/killbill/billing/account/ddl.sql @@ -11,7 +11,7 @@ CREATE TABLE accounts ( currency varchar(3) DEFAULT NULL, billing_cycle_day_local int DEFAULT NULL, payment_method_id varchar(36) DEFAULT NULL, - time_zone varchar(50) DEFAULT NULL, + time_zone varchar(50) NOT NULL, locale varchar(5) DEFAULT NULL, address1 varchar(100) DEFAULT NULL, address2 varchar(100) DEFAULT NULL, @@ -46,7 +46,7 @@ CREATE TABLE account_history ( currency varchar(3) DEFAULT NULL, billing_cycle_day_local int DEFAULT NULL, payment_method_id varchar(36) DEFAULT NULL, - time_zone varchar(50) DEFAULT NULL, + time_zone varchar(50) NOT NULL, locale varchar(5) DEFAULT NULL, address1 varchar(100) DEFAULT NULL, address2 varchar(100) DEFAULT NULL, diff --git a/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java b/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java index 70a3947225..fc3fece0d7 100644 --- a/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java +++ b/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java @@ -76,6 +76,9 @@ public void testMinimalFields() throws Exception { // Verify a default external key was set Assert.assertEquals(retrievedAccount.getExternalKey(), retrievedAccount.getId().toString()); + + // Verify a default time zone was set + Assert.assertEquals(retrievedAccount.getTimeZone(), DateTimeZone.UTC); } @Test(groups = "slow", description = "Test Account: basic DAO calls") From 4110ad6fc674fe5a7a4fcac1173d5e8769a1a485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20G=C3=B3mez?= Date: Mon, 5 Oct 2015 12:17:02 -0300 Subject: [PATCH 108/137] Adding Unit Test to verify the behavior when trying to Cancel a Subscription with an invalid Requested Date --- .../api/user/TestUserApiCancel.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java index 1053238e96..dabfc95d54 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java @@ -233,4 +233,33 @@ public void testUncancel() throws SubscriptionBillingApiException, SubscriptionB assertListenerStatus(); } + + @Test(groups = "slow", expectedExceptions = SubscriptionBaseApiException.class) + public void testCancelSubscriptionWithInvalidRequestedDate() throws SubscriptionBaseApiException { + final String prod = "Shotgun"; + final BillingPeriod term = BillingPeriod.MONTHLY; + final String planSet = PriceListSet.DEFAULT_PRICELIST_NAME; + + // CREATE + final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, prod, term, planSet); + PlanPhase currentPhase = subscription.getCurrentPhase(); + assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + + final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31)); + clock.addDeltaFromReality(it.toDurationMillis()); + assertListenerStatus(); + currentPhase = subscription.getCurrentPhase(); + assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); + + final DateTime invalidDate = subscription.getBundleStartDate().minusDays(3); + // CANCEL in EVERGREEN period with an invalid Date (prior to the Creation Date) + subscription.cancelWithDate(invalidDate, callContext); + assertListenerStatus(); + + testListener.pushExpectedEvent(NextEvent.CANCEL); + assertListenerStatus(); + } } From 7759b81d08a5d42d8fe5d677d2b5706f5101a376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20G=C3=B3mez?= Date: Mon, 5 Oct 2015 13:48:48 -0300 Subject: [PATCH 109/137] Fixing Unit Test for TestUserApiCancel --- .../billing/subscription/api/user/TestUserApiCancel.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java index dabfc95d54..a3acb5000c 100644 --- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java +++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java @@ -257,9 +257,5 @@ public void testCancelSubscriptionWithInvalidRequestedDate() throws Subscription final DateTime invalidDate = subscription.getBundleStartDate().minusDays(3); // CANCEL in EVERGREEN period with an invalid Date (prior to the Creation Date) subscription.cancelWithDate(invalidDate, callContext); - assertListenerStatus(); - - testListener.pushExpectedEvent(NextEvent.CANCEL); - assertListenerStatus(); } } From 25ab39267deb81ab00af325d1289a209d8126f6d Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Mon, 5 Oct 2015 11:54:26 -0700 Subject: [PATCH 110/137] invoice: Implement filtering to opnly return items matching specific dryRun subscriptionId. See #401 This is an extension for 879d013a791910253a72cb6ced7387d2337a532d --- .../integration/TestIntegrationInvoice.java | 1 - .../billing/invoice/InvoiceDispatcher.java | 23 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java index 9e8941ca36..3d81d737c4 100644 --- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java +++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java @@ -189,7 +189,6 @@ public void testDryRunWithNoTargetDateAndMultipleNonAlignedSubscriptions() throw // We test first the next expected invoice for a specific subscription: We can see the targetDate is 2015-2-14 and not 2015-2-1 final DryRunArguments dryRunWIthSubscription = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly.getId(), null, null, null); expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95"))); - expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95"))); dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunWIthSubscription, callContext); assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14)); invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java index 7764c4a09e..49ec8d8318 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -235,12 +236,14 @@ private Invoice processAccountWithLock(final UUID accountId, @Nullable final Dat if (billingEvents.isEmpty()) { return null; } + final Iterable filteredSubscriptionIdsForDryRun = getFilteredSubscriptionIdsForDryRun(dryRunArguments, billingEvents); final List candidateDateTimes = (inputTargetDateTime != null) ? ImmutableList.of(inputTargetDateTime) : - getUpcomingInvoiceCandidateDates(getFilteredSubscriptionIds(dryRunArguments, billingEvents), context); + getUpcomingInvoiceCandidateDates(filteredSubscriptionIdsForDryRun, context); for (final DateTime curTargetDateTime : candidateDateTimes) { final Invoice invoice = processAccountWithLockAndInputTargetDate(accountId, curTargetDateTime, billingEvents, isDryRun, context); if (invoice != null) { + filterInvoiceItemsForDryRun(filteredSubscriptionIdsForDryRun, invoice); return invoice; } } @@ -251,9 +254,23 @@ private Invoice processAccountWithLock(final UUID accountId, @Nullable final Dat } } + private void filterInvoiceItemsForDryRun(final Iterable filteredSubscriptionIdsForDryRun, final Invoice invoice) { + if (!filteredSubscriptionIdsForDryRun.iterator().hasNext()) { + return; + } + + final Iterator it = invoice.getInvoiceItems().iterator(); + while (it.hasNext()) { + final InvoiceItem cur = it.next(); + if (!Iterables.contains(filteredSubscriptionIdsForDryRun, cur.getSubscriptionId())) { + it.remove(); + } + } + } - private Iterable getFilteredSubscriptionIds(final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) { - if (!dryRunArguments.getDryRunType().equals(DryRunType.UPCOMING_INVOICE) || + private Iterable getFilteredSubscriptionIdsForDryRun(@Nullable final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) { + if (dryRunArguments == null || + !dryRunArguments.getDryRunType().equals(DryRunType.UPCOMING_INVOICE) || (dryRunArguments.getSubscriptionId() == null && dryRunArguments.getBundleId() == null)) { return ImmutableList.of(); } From 520bb252a5e9aa2ec0c7190c1d74f38027005010 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Mon, 5 Oct 2015 12:23:17 -0700 Subject: [PATCH 111/137] Update pom with released oss version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b6500b0d60..6f2be439e7 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.46-SNAPSHOT + 0.47 killbill 0.15.6-SNAPSHOT From 2eda0f052c5c04c53b18bf249c02ba489881349b Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 6 Oct 2015 05:34:14 -0700 Subject: [PATCH 112/137] api: fix compilation on 1.6 Reported-by: Bogdan Langendorf Signed-off-by: Pierre-Alexandre Meyer --- api/src/main/java/org/killbill/billing/util/UUIDs.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/killbill/billing/util/UUIDs.java b/api/src/main/java/org/killbill/billing/util/UUIDs.java index 698afd7c52..9a89bddb06 100644 --- a/api/src/main/java/org/killbill/billing/util/UUIDs.java +++ b/api/src/main/java/org/killbill/billing/util/UUIDs.java @@ -149,7 +149,7 @@ private static DigestRandomGenerator sha1Generator() { return new DigestRandomGenerator(MessageDigest.getInstance("SHA-1")); } catch (NoSuchAlgorithmException ex) { - throw new AssertionError("unexpeced missing SHA-1 digest", ex); + throw new Error("unexpeced missing SHA-1 digest", ex); } } From ebeac2aafeca33cf73f068557a551b9f2e84ca1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20G=C3=B3mez?= Date: Tue, 6 Oct 2015 16:10:22 -0300 Subject: [PATCH 113/137] Adding fix for Issue #162. Added Unit Tests and test custom XML catalog --- .../catalog/io/VersionedCatalogLoader.java | 15 +- .../io/TestVersionedCatalogLoader.java | 41 ++++ .../test/resources/com/acme/SpyCarCustom.xml | 189 ++++++++++++++++++ 3 files changed, 231 insertions(+), 14 deletions(-) create mode 100644 catalog/src/test/resources/com/acme/SpyCarCustom.xml diff --git a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java index 5d0e820c68..3404ec7938 100644 --- a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java +++ b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java @@ -63,20 +63,7 @@ public VersionedCatalog loadDefaultCatalog(final String uriString) throws Catalo List xmlURIs; if (uriString.endsWith(XML_EXTENSION)) { // Assume its an xml file xmlURIs = new ArrayList(); - URI uri = new URI(uriString); - - // Try to expand the full path, if possible - final String schemeSpecificPart = uri.getSchemeSpecificPart(); - if (schemeSpecificPart != null) { - final String[] split = schemeSpecificPart.split("/"); - final String fileName = split[split.length - 1]; - try { - uri = new URI(Resources.getResource(fileName).toExternalForm()); - } catch (IllegalArgumentException ignored) { - } - } - - xmlURIs.add(uri); + xmlURIs.add(new URI(uriString)); } else { // Assume its a directory final String directoryContents = UriAccessor.accessUriAsString(uriString); xmlURIs = findXmlReferences(directoryContents, new URL(uriString)); diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java index d6f212ea38..3684484e5d 100644 --- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java +++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java @@ -18,6 +18,7 @@ package org.killbill.billing.catalog.io; +import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; @@ -37,6 +38,7 @@ import org.killbill.billing.catalog.api.InvalidConfigException; import org.testng.Assert; import org.testng.annotations.Test; +import org.weakref.jmx.internal.guava.io.Files; import org.xml.sax.SAXException; import com.google.common.io.Resources; @@ -126,4 +128,43 @@ public void testLoad() throws IOException, SAXException, InvalidConfigException, dt = new DateTime("2011-03-03T00:00:00+00:00"); Assert.assertEquals(it.next().getEffectiveDate(), dt.toDate()); } + + @Test(groups = "fast") + public void testLoadCatalogFromClasspathResourceFolder() throws CatalogApiException { + final VersionedCatalog c = loader.loadDefaultCatalog("SpyCarBasic.xml"); + Assert.assertEquals(c.size(), 1); + DateTime dt = new DateTime("2013-02-08T00:00:00+00:00"); + Assert.assertEquals(c.getEffectiveDate(), dt.toDate()); + Assert.assertEquals(c.getCatalogName(), "SpyCarBasic"); + } + + @Test(groups = "fast", expectedExceptions = CatalogApiException.class) + public void testLoadCatalogFromClasspathResourceBadFolder() throws CatalogApiException { + loader.loadDefaultCatalog("com/acme/SpyCarCustom.xml"); + } + + @Test(groups = "fast") + public void testLoadCatalogFromInsideResourceFolder() throws CatalogApiException, URISyntaxException, IOException { + final VersionedCatalog c = loader.loadDefaultCatalog("com/acme/SpyCarCustom.xml"); + Assert.assertEquals(c.size(), 1); + DateTime dt = new DateTime("2015-10-04T00:00:00+00:00"); + Assert.assertEquals(c.getEffectiveDate(), dt.toDate()); + Assert.assertEquals(c.getCatalogName(), "SpyCarCustom"); + } + + @Test(groups = "fast", expectedExceptions = CatalogApiException.class) + public void testLoadCatalogFromInsideResourceWithBadFolderName() throws CatalogApiException { + loader.loadDefaultCatalog("com/acme2/SpyCarCustom.xml"); + } + + @Test(groups = "fast") + public void testLoadCatalogFromExternalFile() throws CatalogApiException, IOException, URISyntaxException { + URL originURL = Resources.getResource("SpyCarBasic.xml"); + File originFile = new File(originURL.toURI()); + File destinationFile = new File("C:/var/tmp/SpyCarBasic.xml"); + destinationFile.deleteOnExit(); + Files.copy(originFile, destinationFile); + final VersionedCatalog c = loader.loadDefaultCatalog(destinationFile.toURI().toString()); + Assert.assertEquals(c.getCatalogName(), "SpyCarBasic"); + } } diff --git a/catalog/src/test/resources/com/acme/SpyCarCustom.xml b/catalog/src/test/resources/com/acme/SpyCarCustom.xml new file mode 100644 index 0000000000..2d29b02b74 --- /dev/null +++ b/catalog/src/test/resources/com/acme/SpyCarCustom.xml @@ -0,0 +1,189 @@ + + + + + + 2015-10-04T00:00:00+00:00 + SpyCarCustom + + IN_ADVANCE + + + USD + GBP + + + + + BASE + + + BASE + + + BASE + + + + + + + IMMEDIATE + + + + + START_OF_BUNDLE + + + + + IMMEDIATE + + + + + START_OF_BUNDLE + + + + + ACCOUNT + + + + + DEFAULT + + + + + + + Standard + + + + DAYS + 30 + + + + + + + + + + + UNLIMITED + + + MONTHLY + + + GBP + 75.00 + + + USD + 111.00 + + + + + + + Sports + + + + DAYS + 30 + + + + + + + + + + UNLIMITED + + + MONTHLY + + + GBP + 375.00 + + + USD + 511.00 + + + + + + + Super + + + + DAYS + 30 + + + + + + + + + + + UNLIMITED + + + MONTHLY + + + GBP + 750.00 + + + USD + 1111.00 + + + + + + + + + + standard-monthly + sports-monthly + super-monthly + + + + From e36f28a0c0a06ca39305f687e61f8f02bf54b9c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20G=C3=B3mez?= Date: Tue, 6 Oct 2015 16:24:14 -0300 Subject: [PATCH 114/137] Fix Unit Test to match path folders --- .../billing/catalog/io/TestVersionedCatalogLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java index 3684484e5d..7bac7991bc 100644 --- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java +++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java @@ -140,7 +140,7 @@ public void testLoadCatalogFromClasspathResourceFolder() throws CatalogApiExcept @Test(groups = "fast", expectedExceptions = CatalogApiException.class) public void testLoadCatalogFromClasspathResourceBadFolder() throws CatalogApiException { - loader.loadDefaultCatalog("com/acme/SpyCarCustom.xml"); + loader.loadDefaultCatalog("SpyCarCustom.xml"); } @Test(groups = "fast") @@ -161,7 +161,7 @@ public void testLoadCatalogFromInsideResourceWithBadFolderName() throws CatalogA public void testLoadCatalogFromExternalFile() throws CatalogApiException, IOException, URISyntaxException { URL originURL = Resources.getResource("SpyCarBasic.xml"); File originFile = new File(originURL.toURI()); - File destinationFile = new File("C:/var/tmp/SpyCarBasic.xml"); + File destinationFile = new File("/var/tmp/SpyCarBasic.xml"); destinationFile.deleteOnExit(); Files.copy(originFile, destinationFile); final VersionedCatalog c = loader.loadDefaultCatalog(destinationFile.toURI().toString()); From b764b9c60cf24e575adbfc99e7935097dc0c83f9 Mon Sep 17 00:00:00 2001 From: Matias Aguero Date: Tue, 6 Oct 2015 16:34:40 -0300 Subject: [PATCH 115/137] issue 118: Invoice formatter displays now correct currency symbol --- .../formatters/DefaultInvoiceFormatter.java | 52 ++++++-- .../TestDefaultInvoiceFormatter.java | 122 +++++++++++++++++- 2 files changed, 161 insertions(+), 13 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java index 8ae278acfc..14e2bd792a 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java @@ -17,12 +17,15 @@ package org.killbill.billing.invoice.template.formatters; import java.math.BigDecimal; -import java.text.NumberFormat; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.UUID; import org.joda.time.DateTime; @@ -30,11 +33,6 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.killbill.billing.callcontext.InternalTenantContext; -import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory; -import org.killbill.billing.tenant.api.TenantInternalApi; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.killbill.billing.catalog.api.Currency; import org.killbill.billing.currency.api.CurrencyConversion; import org.killbill.billing.currency.api.CurrencyConversionApi; @@ -45,10 +43,13 @@ import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.invoice.api.InvoicePayment; import org.killbill.billing.invoice.api.formatters.InvoiceFormatter; +import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory; import org.killbill.billing.invoice.model.CreditAdjInvoiceItem; import org.killbill.billing.invoice.model.CreditBalanceAdjInvoiceItem; import org.killbill.billing.invoice.model.DefaultInvoice; import org.killbill.billing.util.template.translation.TranslatorConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.base.Objects; import com.google.common.base.Strings; @@ -71,6 +72,18 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter { private final InternalTenantContext context; private final ResourceBundleFactory bundleFactory; + public static Map currencyLocaleMap; + { + currencyLocaleMap = new HashMap(); + for (Locale localeItem : Locale.getAvailableLocales()) { + try { + java.util.Currency currency = java.util.Currency.getInstance(localeItem); + currencyLocaleMap.put(currency, localeItem); + }catch (Exception e){ + } + } + } + public DefaultInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, final CurrencyConversionApi currencyConversionApi, final ResourceBundleFactory bundleFactory, final InternalTenantContext context) { this.config = config; this.invoice = invoice; @@ -213,20 +226,35 @@ public BigDecimal getBalance() { @Override public String getFormattedChargedAmount() { - final NumberFormat number = NumberFormat.getCurrencyInstance(locale); - return number.format(getChargedAmount().doubleValue()); + return getFormattedAmountByLocaleAndInvoiceCurrency(getChargedAmount()); } @Override public String getFormattedPaidAmount() { - final NumberFormat number = NumberFormat.getCurrencyInstance(locale); - return number.format(getPaidAmount().doubleValue()); + return getFormattedAmountByLocaleAndInvoiceCurrency(getPaidAmount()); } @Override public String getFormattedBalance() { - final NumberFormat number = NumberFormat.getCurrencyInstance(locale); - return number.format(getBalance().doubleValue()); + return getFormattedAmountByLocaleAndInvoiceCurrency(getBalance()); + } + + /** + * Returns the amount with the correct currency symbol + * + * @param amount + * @return + */ + private String getFormattedAmountByLocaleAndInvoiceCurrency(BigDecimal amount) { + final DecimalFormat number = (DecimalFormat) DecimalFormat.getCurrencyInstance(locale); + + java.util.Currency currency = java.util.Currency.getInstance(invoice.getCurrency().toString()); + DecimalFormatSymbols dfs = number.getDecimalFormatSymbols(); + dfs.setInternationalCurrencySymbol(currency.getCurrencyCode()); + dfs.setCurrencySymbol(currency.getSymbol(currencyLocaleMap.get(currency))); + number.setDecimalFormatSymbols(dfs); + + return number.format(amount.doubleValue()); } @Override diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java index df0a9d7a08..82008a6a24 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java @@ -34,7 +34,6 @@ import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.invoice.api.InvoicePaymentType; import org.killbill.billing.invoice.api.formatters.InvoiceFormatter; -import org.killbill.billing.invoice.api.formatters.InvoiceFormatterFactory; import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory.ResourceBundleType; import org.killbill.billing.invoice.model.CreditAdjInvoiceItem; import org.killbill.billing.invoice.model.CreditBalanceAdjInvoiceItem; @@ -186,6 +185,127 @@ public void testFormattedAmount() throws Exception { Locale.FRANCE); } + @Test(groups = "fast") + public void testFormattedAmountFranceAndJPY() throws Exception { + + final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("1500.00"), Currency.JPY); + final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.JPY); + invoiceEUR.addInvoiceItem(fixedItem); + + checkOutput(invoiceEUR, + "\n" + + " {{invoice.formattedChargedAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedPaidAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedBalance}}\n" + + "", + "\n" + + " 1 500,00 ¥\n" + + "\n" + + "\n" + + " 0,00 ¥\n" + + "\n" + + "\n" + + " 1 500,00 ¥\n" + + "", + Locale.FRANCE); + } + + @Test(groups = "fast") + public void testFormattedAmountUSAndEUR() throws Exception { + final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("2635.14"), Currency.EUR); + final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR); + invoiceEUR.addInvoiceItem(fixedItemEUR); + + checkOutput(invoiceEUR, + "\n" + + " {{invoice.formattedChargedAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedPaidAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedBalance}}\n" + + "", + "\n" + + " €2,635.14\n" + + "\n" + + "\n" + + " €0.00\n" + + "\n" + + "\n" + + " €2,635.14\n" + + "", + Locale.US); + } + + @Test(groups = "fast") + public void testFormattedAmountUSAndBRL() throws Exception { + final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("2635.14"), Currency.BRL); + final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.BRL); + invoice.addInvoiceItem(fixedItem); + + checkOutput(invoice, + "\n" + + " {{invoice.formattedChargedAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedPaidAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedBalance}}\n" + + "", + "\n" + + " R$2,635.14\n" + + "\n" + + "\n" + + " R$0.00\n" + + "\n" + + "\n" + + " R$2,635.14\n" + + "", + Locale.US); + } + + @Test(groups = "fast") + public void testFormattedAmountUSAndGBP() throws Exception { + final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("1499.95"), Currency.GBP); + final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.GBP); + invoiceEUR.addInvoiceItem(fixedItemEUR); + + checkOutput(invoiceEUR, + "\n" + + " {{invoice.formattedChargedAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedPaidAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedBalance}}\n" + + "", + "\n" + + " £1,499.95\n" + + "\n" + + "\n" + + " £0.00\n" + + "\n" + + "\n" + + " £1,499.95\n" + + "", + Locale.US); + } + @Test(groups = "fast") public void testProcessedCurrencyExists() throws Exception { final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), clock.getUTCNow(), UUID.randomUUID(), new Integer(234), new LocalDate(), new LocalDate(), Currency.BRL, Currency.USD, false); From b0425e65a8374f18020af7c6bae68584497b5d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20G=C3=B3mez?= Date: Tue, 6 Oct 2015 16:38:12 -0300 Subject: [PATCH 116/137] Changing library to Google Guava Files --- .../killbill/billing/catalog/io/TestVersionedCatalogLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java index 7bac7991bc..ca852539e9 100644 --- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java +++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java @@ -38,7 +38,7 @@ import org.killbill.billing.catalog.api.InvalidConfigException; import org.testng.Assert; import org.testng.annotations.Test; -import org.weakref.jmx.internal.guava.io.Files; +import com.google.common.io.Files; import org.xml.sax.SAXException; import com.google.common.io.Resources; From 4a92a941cc0966234cf93a5f3d864f821dfc0104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20G=C3=B3mez?= Date: Tue, 6 Oct 2015 16:49:10 -0300 Subject: [PATCH 117/137] Fix Unit Tests based on comments during Pull Request --- .../billing/catalog/io/TestVersionedCatalogLoader.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java index ca852539e9..4d6a10d8da 100644 --- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java +++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java @@ -38,9 +38,9 @@ import org.killbill.billing.catalog.api.InvalidConfigException; import org.testng.Assert; import org.testng.annotations.Test; -import com.google.common.io.Files; import org.xml.sax.SAXException; +import com.google.common.io.Files; import com.google.common.io.Resources; public class TestVersionedCatalogLoader extends CatalogTestSuiteNoDB { @@ -159,9 +159,8 @@ public void testLoadCatalogFromInsideResourceWithBadFolderName() throws CatalogA @Test(groups = "fast") public void testLoadCatalogFromExternalFile() throws CatalogApiException, IOException, URISyntaxException { - URL originURL = Resources.getResource("SpyCarBasic.xml"); - File originFile = new File(originURL.toURI()); - File destinationFile = new File("/var/tmp/SpyCarBasic.xml"); + File originFile = new File(Resources.getResource("SpyCarBasic.xml").toURI()); + File destinationFile = new File(Files.createTempDir().toString() + "/SpyCarBasicRelocated.xml"); destinationFile.deleteOnExit(); Files.copy(originFile, destinationFile); final VersionedCatalog c = loader.loadDefaultCatalog(destinationFile.toURI().toString()); From ea7d60507e6161747fa0333caadfaeea801b0c45 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Tue, 6 Oct 2015 13:24:20 -0700 Subject: [PATCH 118/137] Update oss version to include latest correction on BillingPeriod changes --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6f2be439e7..064ea57bbd 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.47 + 0.48 killbill 0.15.6-SNAPSHOT From 3d2dfb5dcbd9efa8b03d9480ce8e5bdbf2ebca18 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Tue, 6 Oct 2015 15:27:37 -0700 Subject: [PATCH 119/137] catalog: trivial fixes in TestVersionedCatalogLoader Signed-off-by: Pierre-Alexandre Meyer --- .../catalog/io/TestVersionedCatalogLoader.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java index 4d6a10d8da..ef0d6456ac 100644 --- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java +++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java @@ -1,7 +1,7 @@ /* * Copyright 2010-2013 Ning, Inc. - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -133,7 +133,7 @@ public void testLoad() throws IOException, SAXException, InvalidConfigException, public void testLoadCatalogFromClasspathResourceFolder() throws CatalogApiException { final VersionedCatalog c = loader.loadDefaultCatalog("SpyCarBasic.xml"); Assert.assertEquals(c.size(), 1); - DateTime dt = new DateTime("2013-02-08T00:00:00+00:00"); + final DateTime dt = new DateTime("2013-02-08T00:00:00+00:00"); Assert.assertEquals(c.getEffectiveDate(), dt.toDate()); Assert.assertEquals(c.getCatalogName(), "SpyCarBasic"); } @@ -147,7 +147,7 @@ public void testLoadCatalogFromClasspathResourceBadFolder() throws CatalogApiExc public void testLoadCatalogFromInsideResourceFolder() throws CatalogApiException, URISyntaxException, IOException { final VersionedCatalog c = loader.loadDefaultCatalog("com/acme/SpyCarCustom.xml"); Assert.assertEquals(c.size(), 1); - DateTime dt = new DateTime("2015-10-04T00:00:00+00:00"); + final DateTime dt = new DateTime("2015-10-04T00:00:00+00:00"); Assert.assertEquals(c.getEffectiveDate(), dt.toDate()); Assert.assertEquals(c.getCatalogName(), "SpyCarCustom"); } @@ -159,8 +159,8 @@ public void testLoadCatalogFromInsideResourceWithBadFolderName() throws CatalogA @Test(groups = "fast") public void testLoadCatalogFromExternalFile() throws CatalogApiException, IOException, URISyntaxException { - File originFile = new File(Resources.getResource("SpyCarBasic.xml").toURI()); - File destinationFile = new File(Files.createTempDir().toString() + "/SpyCarBasicRelocated.xml"); + final File originFile = new File(Resources.getResource("SpyCarBasic.xml").toURI()); + final File destinationFile = new File(Files.createTempDir().toString() + "/SpyCarBasicRelocated.xml"); destinationFile.deleteOnExit(); Files.copy(originFile, destinationFile); final VersionedCatalog c = loader.loadDefaultCatalog(destinationFile.toURI().toString()); From 3a0902b0c865ee9d9be4034308665bb37839a229 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 7 Oct 2015 05:38:23 -0700 Subject: [PATCH 120/137] util: make KillbillCredentialsMatcher respect KillbillConfigSource This fixes https://github.com/killbill/killbill/issues/291. Note: this renames the property org.killbill.server.multitenant.hash_iterations to org.killbill.security.shiroNbHashIterations for consistency. Signed-off-by: Pierre-Alexandre Meyer --- .../server/security/KillbillJdbcTenantRealm.java | 7 +++++-- .../billing/server/security/TenantFilter.java | 5 ++++- .../killbill/billing/jaxrs/TestJaxrsBase.java | 4 ++++ .../security/TestKillbillJdbcTenantRealm.java | 4 ++-- .../billing/tenant/dao/DefaultTenantDao.java | 12 +++++++++--- .../tenant/TenantTestSuiteWithEmbeddedDb.java | 16 ++++++++++------ .../billing/tenant/dao/TestDefaultTenantDao.java | 4 ++-- .../glue/TestTenantModuleWithEmbeddedDB.java | 8 ++++++-- .../billing/util/config/SecurityConfig.java | 5 +++++ .../shiro/KillbillCredentialsMatcher.java | 12 ++++++------ .../util/security/shiro/dao/DefaultUserDao.java | 11 +++++++---- .../security/shiro/realm/KillBillJdbcRealm.java | 7 +++++-- .../util/UtilTestSuiteWithEmbeddedDB.java | 3 +++ .../shiro/realm/TestKillBillJdbcRealm.java | 2 +- 14 files changed, 69 insertions(+), 31 deletions(-) diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java index 8955201408..85b17d9bbd 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java @@ -27,6 +27,7 @@ import org.apache.shiro.codec.Base64; import org.apache.shiro.realm.jdbc.JdbcRealm; import org.apache.shiro.util.ByteSource; +import org.killbill.billing.util.config.SecurityConfig; import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher; /** @@ -37,11 +38,13 @@ public class KillbillJdbcTenantRealm extends JdbcRealm { private static final String KILLBILL_AUTHENTICATION_QUERY = "select api_secret, api_salt from tenants where api_key = ?"; private final DataSource dataSource; + private final SecurityConfig securityConfig; - public KillbillJdbcTenantRealm(final DataSource dataSource) { + public KillbillJdbcTenantRealm(final DataSource dataSource, final SecurityConfig securityConfig) { super(); this.dataSource = dataSource; + this.securityConfig = securityConfig; configureSecurity(); configureQueries(); @@ -61,7 +64,7 @@ protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken t private void configureSecurity() { setSaltStyle(SaltStyle.COLUMN); - setCredentialsMatcher(KillbillCredentialsMatcher.getCredentialsMatcher()); + setCredentialsMatcher(KillbillCredentialsMatcher.getCredentialsMatcher(securityConfig)); } private void configureQueries() { diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java index 4fb769e14d..97e2bedfd6 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java @@ -44,6 +44,7 @@ import org.killbill.billing.tenant.api.Tenant; import org.killbill.billing.tenant.api.TenantApiException; import org.killbill.billing.tenant.api.TenantUserApi; +import org.killbill.billing.util.config.SecurityConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +60,8 @@ public class TenantFilter implements Filter { @Inject protected TenantUserApi tenantUserApi; + @Inject + protected SecurityConfig securityConfig; @Inject @Named(KillbillPlatformModule.SHIRO_DATA_SOURCE_ID_NAMED) @@ -68,7 +71,7 @@ public class TenantFilter implements Filter { @Override public void init(final FilterConfig filterConfig) throws ServletException { - final Realm killbillJdbcTenantRealm = new KillbillJdbcTenantRealm(dataSource); + final Realm killbillJdbcTenantRealm = new KillbillJdbcTenantRealm(dataSource, securityConfig); // We use Shiro to verify the api credentials - but the Shiro Subject is only used for RBAC modularRealmAuthenticator = new ModularRealmAuthenticator(); modularRealmAuthenticator.setRealms(ImmutableList.of(killbillJdbcTenantRealm)); diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java index bdfaa5d1e7..fa484200f7 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java @@ -54,6 +54,7 @@ import org.killbill.billing.server.modules.KillbillServerModule; import org.killbill.billing.util.cache.CacheControllerDispatcher; import org.killbill.billing.util.config.PaymentConfig; +import org.killbill.billing.util.config.SecurityConfig; import org.killbill.bus.api.PersistentBus; import org.killbill.commons.jdbi.guice.DaoConfig; import org.skife.config.ConfigurationObjectFactory; @@ -96,6 +97,9 @@ public class TestJaxrsBase extends KillbillClient { @Named(KillbillServerModule.SHIRO_DATA_SOURCE_ID) protected DataSource shiroDataSource; + @Inject + protected SecurityConfig securityConfig; + protected DaoConfig daoConfig; protected KillbillServerConfig serverConfig; diff --git a/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcTenantRealm.java b/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcTenantRealm.java index b3899fdd52..a82fe1ee2e 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcTenantRealm.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcTenantRealm.java @@ -49,7 +49,7 @@ public void beforeMethod() throws Exception { super.beforeMethod(); // Create the tenant - final DefaultTenantDao tenantDao = new DefaultTenantDao(dbi, clock, cacheControllerDispatcher, new DefaultNonEntityDao(dbi)); + final DefaultTenantDao tenantDao = new DefaultTenantDao(dbi, clock, cacheControllerDispatcher, new DefaultNonEntityDao(dbi), securityConfig); tenant = new DefaultTenant(UUID.randomUUID(), null, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString()); tenantDao.create(new TenantModelDao(tenant), internalCallContext); @@ -60,7 +60,7 @@ public void beforeMethod() throws Exception { dbConfig.setUsername(helper.getUsername()); dbConfig.setPassword(helper.getPassword()); - final KillbillJdbcTenantRealm jdbcRealm = new KillbillJdbcTenantRealm(shiroDataSource); + final KillbillJdbcTenantRealm jdbcRealm = new KillbillJdbcTenantRealm(shiroDataSource, securityConfig); jdbcRealm.setDataSource(new HikariDataSource(dbConfig)); securityManager = new DefaultSecurityManager(jdbcRealm); diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java index 14ada6f26d..4d929d4519 100644 --- a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java +++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java @@ -1,7 +1,9 @@ /* * Copyright 2010-2013 Ning, Inc. + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * - * Ning licenses this file to you under the Apache License, version 2.0 + * The Billing Project licenses this file to you 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: * @@ -35,6 +37,7 @@ import org.killbill.billing.tenant.api.TenantKV.TenantKey; import org.killbill.billing.util.UUIDs; import org.killbill.billing.util.cache.CacheControllerDispatcher; +import org.killbill.billing.util.config.SecurityConfig; import org.killbill.billing.util.dao.NonEntityDao; import org.killbill.billing.util.entity.dao.EntityDaoBase; import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper; @@ -56,9 +59,12 @@ public class DefaultTenantDao extends EntityDaoBase() { @Override diff --git a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java index 48b6287bb7..2c4f78f5ba 100644 --- a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java +++ b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java @@ -1,7 +1,9 @@ /* * Copyright 2010-2013 Ning, Inc. + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * - * Ning licenses this file to you under the Apache License, version 2.0 + * The Billing Project licenses this file to you 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: * @@ -18,18 +20,17 @@ import javax.inject.Named; +import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB; import org.killbill.billing.tenant.api.TenantUserApi; -import org.killbill.billing.tenant.dao.NoCachingTenantBroadcastDao; +import org.killbill.billing.tenant.dao.DefaultTenantDao; import org.killbill.billing.tenant.dao.TenantBroadcastDao; import org.killbill.billing.tenant.glue.DefaultTenantModule; +import org.killbill.billing.tenant.glue.TestTenantModuleWithEmbeddedDB; +import org.killbill.billing.util.config.SecurityConfig; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; -import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB; -import org.killbill.billing.tenant.dao.DefaultTenantDao; -import org.killbill.billing.tenant.glue.TestTenantModuleWithEmbeddedDB; - import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; @@ -49,6 +50,9 @@ public class TenantTestSuiteWithEmbeddedDb extends GuicyKillbillTestSuiteWithEmb @Inject protected TenantBroadcastDao tenantBroadcastDao; + @Inject + protected SecurityConfig securityConfig; + @BeforeClass(groups = "slow") protected void beforeClass() throws Exception { final Injector injector = Guice.createInjector(new TestTenantModuleWithEmbeddedDB(configSource)); diff --git a/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java b/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java index d23e86ac73..112e3ba1d5 100644 --- a/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java +++ b/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java @@ -45,11 +45,11 @@ public void testWeCanStoreAndMatchCredentials() throws Exception { // Good combo final AuthenticationToken goodToken = new UsernamePasswordToken(tenant.getApiKey(), tenant.getApiSecret()); - Assert.assertTrue(KillbillCredentialsMatcher.getCredentialsMatcher().doCredentialsMatch(goodToken, authenticationInfo)); + Assert.assertTrue(KillbillCredentialsMatcher.getCredentialsMatcher(securityConfig).doCredentialsMatch(goodToken, authenticationInfo)); // Bad combo final AuthenticationToken badToken = new UsernamePasswordToken(tenant.getApiKey(), tenant.getApiSecret() + "T"); - Assert.assertFalse(KillbillCredentialsMatcher.getCredentialsMatcher().doCredentialsMatch(badToken, authenticationInfo)); + Assert.assertFalse(KillbillCredentialsMatcher.getCredentialsMatcher(securityConfig).doCredentialsMatch(badToken, authenticationInfo)); } @Test(groups = "slow") diff --git a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleWithEmbeddedDB.java b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleWithEmbeddedDB.java index c2acf956fb..0bae0d124f 100644 --- a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleWithEmbeddedDB.java +++ b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleWithEmbeddedDB.java @@ -1,7 +1,7 @@ /* * Copyright 2010-2013 Ning, Inc. - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -21,6 +21,8 @@ import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule; import org.killbill.billing.platform.api.KillbillConfigSource; import org.killbill.billing.util.glue.NonEntityDaoModule; +import org.killbill.billing.util.glue.SecurityModule; +import org.killbill.billing.util.glue.TestUtilModuleNoDB.ShiroModuleNoDB; public class TestTenantModuleWithEmbeddedDB extends TestTenantModule { @@ -34,5 +36,7 @@ public void configure() { install(new GuicyKillbillTestWithEmbeddedDBModule(configSource)); install(new NonEntityDaoModule(configSource)); + install(new SecurityModule(configSource)); + install(new ShiroModuleNoDB(configSource)); } } diff --git a/util/src/main/java/org/killbill/billing/util/config/SecurityConfig.java b/util/src/main/java/org/killbill/billing/util/config/SecurityConfig.java index d53b6c602f..3b2382dcbe 100644 --- a/util/src/main/java/org/killbill/billing/util/config/SecurityConfig.java +++ b/util/src/main/java/org/killbill/billing/util/config/SecurityConfig.java @@ -28,6 +28,11 @@ public interface SecurityConfig extends KillbillConfig { @Description("Path to the shiro.ini file (classpath, url or file resource)") public String getShiroResourcePath(); + @Config("org.killbill.security.shiroNbHashIterations") + @Default("200000") + @Description("Sets the number of times submitted credentials will be hashed before comparing to the credentials stored in the system") + public Integer getShiroNbHashIterations(); + // LDAP Realm @Config("org.killbill.security.ldap.userDnTemplate") diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java b/util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java index 7b5bc12cf8..1830226ef9 100644 --- a/util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java +++ b/util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java @@ -1,7 +1,9 @@ /* * Copyright 2010-2013 Ning, Inc. + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * - * Ning licenses this file to you under the Apache License, version 2.0 + * The Billing Project licenses this file to you 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: * @@ -19,23 +21,21 @@ import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.crypto.hash.Sha512Hash; +import org.killbill.billing.util.config.SecurityConfig; public class KillbillCredentialsMatcher { - public static final String KILLBILL_TENANT_HASH_ITERATIONS_PROPERTY = "org.killbill.server.multitenant.hash_iterations"; - // See http://www.stormpath.com/blog/strong-password-hashing-apache-shiro and https://issues.apache.org/jira/browse/SHIRO-290 public static final String HASH_ALGORITHM_NAME = Sha512Hash.ALGORITHM_NAME; - public static final Integer HASH_ITERATIONS = Integer.parseInt(System.getProperty(KILLBILL_TENANT_HASH_ITERATIONS_PROPERTY, "200000")); private KillbillCredentialsMatcher() {} - public static CredentialsMatcher getCredentialsMatcher() { + public static CredentialsMatcher getCredentialsMatcher(final SecurityConfig securityConfig) { // This needs to be in sync with DefaultTenantDao final HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(HASH_ALGORITHM_NAME); // base64 encoding, not hex credentialsMatcher.setStoredCredentialsHexEncoded(false); - credentialsMatcher.setHashIterations(HASH_ITERATIONS); + credentialsMatcher.setHashIterations(securityConfig.getShiroNbHashIterations()); return credentialsMatcher; } diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java index 92cc4f961a..28b9ca32cb 100644 --- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java +++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java @@ -28,6 +28,7 @@ import org.joda.time.DateTime; import org.killbill.billing.ErrorCode; import org.killbill.billing.security.SecurityApiException; +import org.killbill.billing.util.config.SecurityConfig; import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher; import org.killbill.clock.Clock; import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory; @@ -43,17 +44,19 @@ public class DefaultUserDao implements UserDao { private static final RandomNumberGenerator rng = new SecureRandomNumberGenerator(); + private final IDBI dbi; private final Clock clock; + private final SecurityConfig securityConfig; @Inject - public DefaultUserDao(final IDBI dbi, final Clock clock) { + public DefaultUserDao(final IDBI dbi, final Clock clock, final SecurityConfig securityConfig) { this.dbi = dbi; this.clock = clock; + this.securityConfig = securityConfig; ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(UserModelDao.class)); ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(UserRolesModelDao.class)); ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(RolesPermissionsModelDao.class)); - } @Override @@ -61,7 +64,7 @@ public void insertUser(final String username, final String password, final List< final ByteSource salt = rng.nextBytes(); final String hashedPasswordBase64 = new SimpleHash(KillbillCredentialsMatcher.HASH_ALGORITHM_NAME, - password, salt.toBase64(), KillbillCredentialsMatcher.HASH_ITERATIONS).toBase64(); + password, salt.toBase64(), securityConfig.getShiroNbHashIterations()).toBase64(); final DateTime createdDate = clock.getUTCNow(); dbi.inTransaction(new TransactionCallback() { @@ -136,7 +139,7 @@ public void updateUserPassword(final String username, final String password, fin final ByteSource salt = rng.nextBytes(); final String hashedPasswordBase64 = new SimpleHash(KillbillCredentialsMatcher.HASH_ALGORITHM_NAME, - password, salt.toBase64(), KillbillCredentialsMatcher.HASH_ITERATIONS).toBase64(); + password, salt.toBase64(), securityConfig.getShiroNbHashIterations()).toBase64(); dbi.inTransaction(new TransactionCallback() { @Override diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java index 9806abe4bb..380258ba24 100644 --- a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java +++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java @@ -24,6 +24,7 @@ import org.apache.shiro.realm.jdbc.JdbcRealm; import org.apache.shiro.subject.PrincipalCollection; import org.killbill.billing.platform.glue.KillBillPlatformModuleBase; +import org.killbill.billing.util.config.SecurityConfig; import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher; public class KillBillJdbcRealm extends JdbcRealm { @@ -33,11 +34,13 @@ public class KillBillJdbcRealm extends JdbcRealm { protected static final String KILLBILL_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ? and is_active"; private final DataSource dataSource; + private final SecurityConfig securityConfig; @Inject - public KillBillJdbcRealm(@Named(KillBillPlatformModuleBase.SHIRO_DATA_SOURCE_ID_NAMED) final DataSource dataSource) { + public KillBillJdbcRealm(@Named(KillBillPlatformModuleBase.SHIRO_DATA_SOURCE_ID_NAMED) final DataSource dataSource, final SecurityConfig securityConfig) { super(); this.dataSource = dataSource; + this.securityConfig = securityConfig; // Tweak JdbcRealm defaults setPermissionsLookupEnabled(true); @@ -56,7 +59,7 @@ public void clearCachedAuthorizationInfo(PrincipalCollection principals) { private void configureSecurity() { setSaltStyle(SaltStyle.COLUMN); - setCredentialsMatcher(KillbillCredentialsMatcher.getCredentialsMatcher()); + setCredentialsMatcher(KillbillCredentialsMatcher.getCredentialsMatcher(securityConfig)); } private void configureDataSource() { diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java index df64546b34..6bdbbb32a6 100644 --- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java +++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java @@ -26,6 +26,7 @@ import org.killbill.billing.util.audit.dao.AuditDao; import org.killbill.billing.util.cache.CacheControllerDispatcher; import org.killbill.billing.util.callcontext.InternalCallContextFactory; +import org.killbill.billing.util.config.SecurityConfig; import org.killbill.billing.util.customfield.api.DefaultCustomFieldUserApi; import org.killbill.billing.util.customfield.dao.CustomFieldDao; import org.killbill.billing.util.dao.NonEntityDao; @@ -84,6 +85,8 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite protected TestApiListener eventsListener; @Inject protected SecurityApi securityApi; + @Inject + protected SecurityConfig securityConfig; @BeforeClass(groups = "slow") public void beforeClass() throws Exception { diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java index 540f375d32..931e546539 100644 --- a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java +++ b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java @@ -50,7 +50,7 @@ public class TestKillBillJdbcRealm extends UtilTestSuiteWithEmbeddedDB { @BeforeMethod(groups = "slow") public void beforeMethod() throws Exception { super.beforeMethod(); - final KillBillJdbcRealm realm = new KillBillJdbcRealm(helper.getDataSource()); + final KillBillJdbcRealm realm = new KillBillJdbcRealm(helper.getDataSource(), securityConfig); securityManager = new DefaultSecurityManager(realm); SecurityUtils.setSecurityManager(securityManager); } From d0ad5c2e0fdfe43f7f6cdea08a32aacb3cef891b Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 7 Oct 2015 09:32:39 -0700 Subject: [PATCH 121/137] server: update killbill.properties with new property name Signed-off-by: Pierre-Alexandre Meyer --- profiles/killbill/src/test/resources/killbill.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiles/killbill/src/test/resources/killbill.properties b/profiles/killbill/src/test/resources/killbill.properties index 470500ce08..08ec536775 100644 --- a/profiles/killbill/src/test/resources/killbill.properties +++ b/profiles/killbill/src/test/resources/killbill.properties @@ -27,4 +27,4 @@ org.killbill.payment.retry.days=8,8,8 org.killbill.osgi.bundle.install.dir=/var/tmp/somethingthatdoesnotexist # Speed up from the (more secure) default -org.killbill.server.multitenant.hash_iterations=10 +org.killbill.security.shiroNbHashIterations=10 From 43afb0759795e3ffdf748925e592b65738e00f25 Mon Sep 17 00:00:00 2001 From: Matias Aguero Date: Wed, 7 Oct 2015 15:55:17 -0300 Subject: [PATCH 122/137] Changes made according pull reques discussion. https://github.com/killbill/killbill/pull/405 --- .../formatters/DefaultInvoiceFormatter.java | 71 ++++++++-------- .../DefaultInvoiceFormatterFactory.java | 17 +++- .../DefaultInvoiceItemFormatter.java | 8 +- .../TestDefaultInvoiceFormatter.java | 83 ++++++++++++++----- 4 files changed, 117 insertions(+), 62 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java index 14e2bd792a..a3fed268b4 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java @@ -21,13 +21,13 @@ import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; +import org.joda.money.CurrencyUnit; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; @@ -55,8 +55,6 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; -import static org.killbill.billing.util.DefaultAmountFormatter.round; - /** * Format invoice fields */ @@ -71,20 +69,11 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter { private final CurrencyConversionApi currencyConversionApi; private final InternalTenantContext context; private final ResourceBundleFactory bundleFactory; + private Map currencyLocaleMap; - public static Map currencyLocaleMap; - { - currencyLocaleMap = new HashMap(); - for (Locale localeItem : Locale.getAvailableLocales()) { - try { - java.util.Currency currency = java.util.Currency.getInstance(localeItem); - currencyLocaleMap.put(currency, localeItem); - }catch (Exception e){ - } - } - } - - public DefaultInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, final CurrencyConversionApi currencyConversionApi, final ResourceBundleFactory bundleFactory, final InternalTenantContext context) { + public DefaultInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, + final CurrencyConversionApi currencyConversionApi, final ResourceBundleFactory bundleFactory, + final InternalTenantContext context, Map currencyLocaleMap) { this.config = config; this.invoice = invoice; this.dateFormatter = DateTimeFormat.mediumDate().withLocale(locale); @@ -92,6 +81,7 @@ public DefaultInvoiceFormatter(final TranslatorConfig config, final Invoice invo this.currencyConversionApi = currencyConversionApi; this.bundleFactory = bundleFactory; this.context = context; + this.currencyLocaleMap = currencyLocaleMap; } @Override @@ -211,17 +201,17 @@ public UUID getAccountId() { @Override public BigDecimal getChargedAmount() { - return round(Objects.firstNonNull(invoice.getChargedAmount(), BigDecimal.ZERO)); + return Objects.firstNonNull(invoice.getChargedAmount(), BigDecimal.ZERO); } @Override public BigDecimal getOriginalChargedAmount() { - return round(Objects.firstNonNull(invoice.getOriginalChargedAmount(), BigDecimal.ZERO)); + return Objects.firstNonNull(invoice.getOriginalChargedAmount(), BigDecimal.ZERO); } @Override public BigDecimal getBalance() { - return round(Objects.firstNonNull(invoice.getBalance(), BigDecimal.ZERO)); + return Objects.firstNonNull(invoice.getBalance(), BigDecimal.ZERO); } @Override @@ -239,22 +229,28 @@ public String getFormattedBalance() { return getFormattedAmountByLocaleAndInvoiceCurrency(getBalance()); } - /** - * Returns the amount with the correct currency symbol - * - * @param amount - * @return - */ + // Returns the formatted amount with the correct currency symbol that is get from the invoice currency. private String getFormattedAmountByLocaleAndInvoiceCurrency(BigDecimal amount) { - final DecimalFormat number = (DecimalFormat) DecimalFormat.getCurrencyInstance(locale); - java.util.Currency currency = java.util.Currency.getInstance(invoice.getCurrency().toString()); - DecimalFormatSymbols dfs = number.getDecimalFormatSymbols(); - dfs.setInternationalCurrencySymbol(currency.getCurrencyCode()); - dfs.setCurrencySymbol(currency.getSymbol(currencyLocaleMap.get(currency))); - number.setDecimalFormatSymbols(dfs); + String invoiceCurrencyCode = invoice.getCurrency().toString(); + CurrencyUnit currencyUnit = CurrencyUnit.of(invoiceCurrencyCode); + + final DecimalFormat numberFormatter = (DecimalFormat) DecimalFormat.getCurrencyInstance(locale); + DecimalFormatSymbols dfs = numberFormatter.getDecimalFormatSymbols(); + dfs.setInternationalCurrencySymbol(currencyUnit.getCurrencyCode()); - return number.format(amount.doubleValue()); + try { + final java.util.Currency currency = java.util.Currency.getInstance(invoiceCurrencyCode); + dfs.setCurrencySymbol(currency.getSymbol(currencyLocaleMap.get(currency))); + } catch (Exception e) { + dfs.setCurrencySymbol(currencyUnit.getSymbol(locale)); + } + + numberFormatter.setDecimalFormatSymbols(dfs); + numberFormatter.setMinimumFractionDigits(currencyUnit.getDefaultFractionDigits()); + numberFormatter.setMaximumFractionDigits(currencyUnit.getDefaultFractionDigits()); + + return numberFormatter.format(amount.doubleValue()); } @Override @@ -316,7 +312,7 @@ public Currency getCurrency() { @Override public BigDecimal getPaidAmount() { - return round(Objects.firstNonNull(invoice.getPaidAmount(), BigDecimal.ZERO)); + return Objects.firstNonNull(invoice.getPaidAmount(), BigDecimal.ZERO); } @Override @@ -365,13 +361,18 @@ protected Invoice getInvoice() { return invoice; } + @SuppressWarnings("UnusedDeclaration") + protected Map getCurrencyLocaleMap() { + return currencyLocaleMap; + } + @Override public BigDecimal getCreditedAmount() { - return round(Objects.firstNonNull(invoice.getCreditedAmount(), BigDecimal.ZERO)); + return Objects.firstNonNull(invoice.getCreditedAmount(), BigDecimal.ZERO); } @Override public BigDecimal getRefundedAmount() { - return round(Objects.firstNonNull(invoice.getRefundedAmount(), BigDecimal.ZERO)); + return Objects.firstNonNull(invoice.getRefundedAmount(), BigDecimal.ZERO); } } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java index 890fb30338..6496df73f5 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java @@ -16,7 +16,10 @@ package org.killbill.billing.invoice.template.formatters; +import java.util.Currency; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import org.killbill.billing.callcontext.InternalTenantContext; import org.killbill.billing.currency.api.CurrencyConversionApi; @@ -24,7 +27,6 @@ import org.killbill.billing.invoice.api.formatters.InvoiceFormatter; import org.killbill.billing.invoice.api.formatters.InvoiceFormatterFactory; import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory; -import org.killbill.billing.tenant.api.TenantInternalApi; import org.killbill.billing.util.template.translation.TranslatorConfig; public class DefaultInvoiceFormatterFactory implements InvoiceFormatterFactory { @@ -32,6 +34,17 @@ public class DefaultInvoiceFormatterFactory implements InvoiceFormatterFactory { @Override public InvoiceFormatter createInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, CurrencyConversionApi currencyConversionApi, final ResourceBundleFactory bundleFactory, final InternalTenantContext context) { - return new DefaultInvoiceFormatter(config, invoice, locale, currencyConversionApi, bundleFactory, context); + + // this initialization relies on System.currentTimeMillis() instead of the Kill Bill clock (it won't be accurate when moving the clock) + Map currencyLocaleMap = new HashMap(); + for (Locale localeItem : Locale.getAvailableLocales()) { + try { + java.util.Currency currency = java.util.Currency.getInstance(localeItem); + currencyLocaleMap.put(currency, localeItem); + }catch (Exception e){ + } + } + + return new DefaultInvoiceFormatter(config, invoice, locale, currencyConversionApi, bundleFactory, context, currencyLocaleMap); } } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java index 51ddcc637a..5516c8ca90 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java @@ -36,14 +36,10 @@ import org.killbill.billing.util.template.translation.DefaultCatalogTranslator; import org.killbill.billing.util.template.translation.Translator; import org.killbill.billing.util.template.translation.TranslatorConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.common.base.Objects; import com.google.common.base.Strings; -import static org.killbill.billing.util.DefaultAmountFormatter.round; - /** * Format invoice item fields */ @@ -71,7 +67,7 @@ public DefaultInvoiceItemFormatter(final TranslatorConfig config, @Override public BigDecimal getAmount() { - return round(Objects.firstNonNull(item.getAmount(), BigDecimal.ZERO)); + return Objects.firstNonNull(item.getAmount(), BigDecimal.ZERO); } @Override @@ -168,7 +164,7 @@ public DateTime getUpdatedDate() { @Override public BigDecimal getRate() { - return round(BigDecimal.ZERO); + return BigDecimal.ZERO; } @Override diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java index 82008a6a24..6d5cc0afb0 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java @@ -88,7 +88,7 @@ public void testIgnoreZeroAdjustments() throws Exception { Assert.assertEquals(invoice.getCreditedAmount().doubleValue(), 0.00); // Verify the merge - final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext); + final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext, getAvailableLocales()); final List invoiceItems = formatter.getInvoiceItems(); Assert.assertEquals(invoiceItems.size(), 1); Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED); @@ -142,7 +142,7 @@ public void testMergeItems() throws Exception { Assert.assertEquals(invoice.getRefundedAmount().doubleValue(), -1.00); // Verify the merge - final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext); + final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext, getAvailableLocales()); final List invoiceItems = formatter.getInvoiceItems(); Assert.assertEquals(invoiceItems.size(), 4); Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED); @@ -159,7 +159,7 @@ public void testMergeItems() throws Exception { public void testFormattedAmount() throws Exception { final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), - new LocalDate(), new BigDecimal("1499.95"), Currency.EUR); + new LocalDate(), new BigDecimal("1499.952"), Currency.EUR); final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR); invoiceEUR.addInvoiceItem(fixedItemEUR); @@ -191,10 +191,10 @@ public void testFormattedAmountFranceAndJPY() throws Exception { final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), new LocalDate(), new BigDecimal("1500.00"), Currency.JPY); - final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.JPY); - invoiceEUR.addInvoiceItem(fixedItem); + final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.JPY); + invoice.addInvoiceItem(fixedItem); - checkOutput(invoiceEUR, + checkOutput(invoice, "\n" + " {{invoice.formattedChargedAmount}}\n" + "\n" + @@ -205,26 +205,57 @@ public void testFormattedAmountFranceAndJPY() throws Exception { " {{invoice.formattedBalance}}\n" + "", "\n" + - " 1 500,00 ¥\n" + + " 1 500 ¥\n" + "\n" + "\n" + - " 0,00 ¥\n" + + " 0 ¥\n" + "\n" + "\n" + - " 1 500,00 ¥\n" + + " 1 500 ¥\n" + "", Locale.FRANCE); } + @Test(groups = "fast") + public void testFormattedAmountUSAndBTC() throws Exception { + + final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("1105.28843439"), Currency.BTC); + final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.BTC); + invoice.addInvoiceItem(fixedItem); + + checkOutput(invoice, + "\n" + + " {{invoice.formattedChargedAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedPaidAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedBalance}}\n" + + "", + "\n" + + " BTC1,105.28843439\n" + + "\n" + + "\n" + + " BTC0.00000000\n" + + "\n" + + "\n" + + " BTC1,105.28843439\n" + + "", + Locale.US); + } + @Test(groups = "fast") public void testFormattedAmountUSAndEUR() throws Exception { - final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, + final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), new LocalDate(), new BigDecimal("2635.14"), Currency.EUR); - final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR); - invoiceEUR.addInvoiceItem(fixedItemEUR); + final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR); + invoice.addInvoiceItem(fixedItem); - checkOutput(invoiceEUR, + checkOutput(invoice, "\n" + " {{invoice.formattedChargedAmount}}\n" + "\n" + @@ -278,13 +309,13 @@ public void testFormattedAmountUSAndBRL() throws Exception { @Test(groups = "fast") public void testFormattedAmountUSAndGBP() throws Exception { - final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, + final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), new LocalDate(), new BigDecimal("1499.95"), Currency.GBP); - final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.GBP); - invoiceEUR.addInvoiceItem(fixedItemEUR); + final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.GBP); + invoice.addInvoiceItem(fixedItem); - checkOutput(invoiceEUR, + checkOutput(invoice, "\n" + " {{invoice.formattedChargedAmount}}\n" + "\n" + @@ -449,7 +480,7 @@ public void testForDisplay() throws Exception { data.put("text", translator); - data.put("invoice", new DefaultInvoiceFormatter(config, invoice, Locale.US, currencyConversionApi, resourceBundleFactory, internalCallContext)); + data.put("invoice", new DefaultInvoiceFormatter(config, invoice, Locale.US, currencyConversionApi, resourceBundleFactory, internalCallContext, getAvailableLocales())); final String formattedText = templateEngine.executeTemplateText(template, data); @@ -458,9 +489,23 @@ public void testForDisplay() throws Exception { private void checkOutput(final Invoice invoice, final String template, final String expected, final Locale locale) { final Map data = new HashMap(); - data.put("invoice", new DefaultInvoiceFormatter(config, invoice, locale, null, resourceBundleFactory, internalCallContext)); + data.put("invoice", new DefaultInvoiceFormatter(config, invoice, locale, null, resourceBundleFactory, internalCallContext, getAvailableLocales())); final String formattedText = templateEngine.executeTemplateText(template, data); Assert.assertEquals(formattedText, expected); } + + private Map getAvailableLocales() { + Map currencyLocaleMap = new HashMap(); + + currencyLocaleMap.put(java.util.Currency.getInstance(Locale.US), Locale.US); + currencyLocaleMap.put(java.util.Currency.getInstance(Locale.FRANCE), Locale.FRANCE); + currencyLocaleMap.put(java.util.Currency.getInstance(Locale.JAPAN), Locale.JAPAN); + currencyLocaleMap.put(java.util.Currency.getInstance(Locale.UK), Locale.UK); + Locale brLocale = new Locale("pt", "BR"); + currencyLocaleMap.put(java.util.Currency.getInstance(brLocale), brLocale); + + return currencyLocaleMap; + } + } From 896ec16586e33e5867bbba8eaa21392042704149 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 7 Oct 2015 13:38:24 -0700 Subject: [PATCH 123/137] invoice: trivial cleanups in formatters Signed-off-by: Pierre-Alexandre Meyer --- .../formatters/DefaultInvoiceFormatter.java | 36 ++++++++++--------- .../TestDefaultInvoiceFormatter.java | 22 ++++++------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java index a3fed268b4..b5801840a8 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java @@ -1,7 +1,9 @@ /* * Copyright 2010-2013 Ning, Inc. + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * - * Ning licenses this file to you under the Apache License, version 2.0 + * The Billing Project licenses this file to you 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: * @@ -51,7 +53,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Objects; +import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; @@ -60,7 +62,7 @@ */ public class DefaultInvoiceFormatter implements InvoiceFormatter { - private final static Logger logger = LoggerFactory.getLogger(DefaultInvoiceFormatter.class); + private static final Logger logger = LoggerFactory.getLogger(DefaultInvoiceFormatter.class); private final TranslatorConfig config; private final Invoice invoice; @@ -69,11 +71,11 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter { private final CurrencyConversionApi currencyConversionApi; private final InternalTenantContext context; private final ResourceBundleFactory bundleFactory; - private Map currencyLocaleMap; + private final Map currencyLocaleMap; public DefaultInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, final CurrencyConversionApi currencyConversionApi, final ResourceBundleFactory bundleFactory, - final InternalTenantContext context, Map currencyLocaleMap) { + final InternalTenantContext context, final Map currencyLocaleMap) { this.config = config; this.invoice = invoice; this.dateFormatter = DateTimeFormat.mediumDate().withLocale(locale); @@ -86,7 +88,7 @@ public DefaultInvoiceFormatter(final TranslatorConfig config, final Invoice invo @Override public Integer getInvoiceNumber() { - return Objects.firstNonNull(invoice.getInvoiceNumber(), 0); + return MoreObjects.firstNonNull(invoice.getInvoiceNumber(), 0); } @Override @@ -166,7 +168,7 @@ public boolean addInvoiceItems(final Collection items) { @Override public List getInvoiceItems(final Class clazz) { - return Objects.firstNonNull(invoice.getInvoiceItems(clazz), ImmutableList.of()); + return MoreObjects.firstNonNull(invoice.getInvoiceItems(clazz), ImmutableList.of()); } @Override @@ -186,7 +188,7 @@ public boolean addPayments(final Collection payments) { @Override public List getPayments() { - return Objects.firstNonNull(invoice.getPayments(), ImmutableList.of()); + return MoreObjects.firstNonNull(invoice.getPayments(), ImmutableList.of()); } @Override @@ -201,17 +203,17 @@ public UUID getAccountId() { @Override public BigDecimal getChargedAmount() { - return Objects.firstNonNull(invoice.getChargedAmount(), BigDecimal.ZERO); + return MoreObjects.firstNonNull(invoice.getChargedAmount(), BigDecimal.ZERO); } @Override public BigDecimal getOriginalChargedAmount() { - return Objects.firstNonNull(invoice.getOriginalChargedAmount(), BigDecimal.ZERO); + return MoreObjects.firstNonNull(invoice.getOriginalChargedAmount(), BigDecimal.ZERO); } @Override public BigDecimal getBalance() { - return Objects.firstNonNull(invoice.getBalance(), BigDecimal.ZERO); + return MoreObjects.firstNonNull(invoice.getBalance(), BigDecimal.ZERO); } @Override @@ -268,7 +270,7 @@ public String getProcessedPaymentRate() { } // If there were multiple payments (and refunds) we pick chose the last one DateTime latestPaymentDate = null; - final Iterator paymentIterator = ((DefaultInvoice) invoice).getPayments().iterator(); + final Iterator paymentIterator = invoice.getPayments().iterator(); while (paymentIterator.hasNext()) { final InvoicePayment cur = paymentIterator.next(); latestPaymentDate = latestPaymentDate != null && latestPaymentDate.isAfter(cur.getPaymentDate()) ? @@ -277,12 +279,12 @@ public String getProcessedPaymentRate() { } try { final CurrencyConversion conversion = currencyConversionApi.getCurrencyConversion(currency, latestPaymentDate); - for (Rate rate : conversion.getRates()) { + for (final Rate rate : conversion.getRates()) { if (rate.getCurrency() == getCurrency()) { return rate.getValue().toString(); } } - } catch (CurrencyConversionException e) { + } catch (final CurrencyConversionException e) { logger.warn("Failed to retrieve currency conversion rates for currency = " + currency + " and date = " + latestPaymentDate, e); return null; } @@ -312,7 +314,7 @@ public Currency getCurrency() { @Override public BigDecimal getPaidAmount() { - return Objects.firstNonNull(invoice.getPaidAmount(), BigDecimal.ZERO); + return MoreObjects.firstNonNull(invoice.getPaidAmount(), BigDecimal.ZERO); } @Override @@ -368,11 +370,11 @@ protected Map getCurrencyLocaleMap() { @Override public BigDecimal getCreditedAmount() { - return Objects.firstNonNull(invoice.getCreditedAmount(), BigDecimal.ZERO); + return MoreObjects.firstNonNull(invoice.getCreditedAmount(), BigDecimal.ZERO); } @Override public BigDecimal getRefundedAmount() { - return Objects.firstNonNull(invoice.getRefundedAmount(), BigDecimal.ZERO); + return MoreObjects.firstNonNull(invoice.getRefundedAmount(), BigDecimal.ZERO); } } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java index 6d5cc0afb0..3f1b7d0e8a 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java @@ -1,7 +1,7 @@ /* * Copyright 2010-2013 Ning, Inc. - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -187,10 +187,9 @@ public void testFormattedAmount() throws Exception { @Test(groups = "fast") public void testFormattedAmountFranceAndJPY() throws Exception { - final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, - UUID.randomUUID().toString(), UUID.randomUUID().toString(), - new LocalDate(), new BigDecimal("1500.00"), Currency.JPY); + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("1500.00"), Currency.JPY); final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.JPY); invoice.addInvoiceItem(fixedItem); @@ -218,7 +217,6 @@ public void testFormattedAmountFranceAndJPY() throws Exception { @Test(groups = "fast") public void testFormattedAmountUSAndBTC() throws Exception { - final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), new LocalDate(), new BigDecimal("1105.28843439"), Currency.BTC); @@ -250,8 +248,8 @@ public void testFormattedAmountUSAndBTC() throws Exception { @Test(groups = "fast") public void testFormattedAmountUSAndEUR() throws Exception { final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, - UUID.randomUUID().toString(), UUID.randomUUID().toString(), - new LocalDate(), new BigDecimal("2635.14"), Currency.EUR); + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("2635.14"), Currency.EUR); final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR); invoice.addInvoiceItem(fixedItem); @@ -280,8 +278,8 @@ public void testFormattedAmountUSAndEUR() throws Exception { @Test(groups = "fast") public void testFormattedAmountUSAndBRL() throws Exception { final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, - UUID.randomUUID().toString(), UUID.randomUUID().toString(), - new LocalDate(), new BigDecimal("2635.14"), Currency.BRL); + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("2635.14"), Currency.BRL); final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.BRL); invoice.addInvoiceItem(fixedItem); @@ -310,8 +308,8 @@ public void testFormattedAmountUSAndBRL() throws Exception { @Test(groups = "fast") public void testFormattedAmountUSAndGBP() throws Exception { final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, - UUID.randomUUID().toString(), UUID.randomUUID().toString(), - new LocalDate(), new BigDecimal("1499.95"), Currency.GBP); + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("1499.95"), Currency.GBP); final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.GBP); invoice.addInvoiceItem(fixedItem); From 8ea9e88c4d4b6818801e07295315f2d6777cf286 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 7 Oct 2015 13:39:06 -0700 Subject: [PATCH 124/137] invoice: share the currency -> locale map in tests Signed-off-by: Pierre-Alexandre Meyer --- .../formatters/DefaultInvoiceFormatter.java | 5 ---- .../DefaultInvoiceFormatterFactory.java | 25 +++++++++++++------ .../TestDefaultInvoiceFormatter.java | 24 +++++------------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java index b5801840a8..b12e189241 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java @@ -363,11 +363,6 @@ protected Invoice getInvoice() { return invoice; } - @SuppressWarnings("UnusedDeclaration") - protected Map getCurrencyLocaleMap() { - return currencyLocaleMap; - } - @Override public BigDecimal getCreditedAmount() { return MoreObjects.firstNonNull(invoice.getCreditedAmount(), BigDecimal.ZERO); diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java index 6496df73f5..b9e405bb64 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java @@ -29,22 +29,31 @@ import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory; import org.killbill.billing.util.template.translation.TranslatorConfig; +import com.google.common.annotations.VisibleForTesting; + public class DefaultInvoiceFormatterFactory implements InvoiceFormatterFactory { - @Override - public InvoiceFormatter createInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, CurrencyConversionApi currencyConversionApi, - final ResourceBundleFactory bundleFactory, final InternalTenantContext context) { + private final Map currencyLocaleMap = new HashMap(); - // this initialization relies on System.currentTimeMillis() instead of the Kill Bill clock (it won't be accurate when moving the clock) - Map currencyLocaleMap = new HashMap(); - for (Locale localeItem : Locale.getAvailableLocales()) { + public DefaultInvoiceFormatterFactory() { + // This initialization relies on System.currentTimeMillis() instead of the Kill Bill clock (it won't be accurate when moving the clock) + for (final Locale localeItem : Locale.getAvailableLocales()) { try { - java.util.Currency currency = java.util.Currency.getInstance(localeItem); + final java.util.Currency currency = java.util.Currency.getInstance(localeItem); currencyLocaleMap.put(currency, localeItem); - }catch (Exception e){ + } catch (final Exception ignored) { } } + } + @Override + public InvoiceFormatter createInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, final CurrencyConversionApi currencyConversionApi, + final ResourceBundleFactory bundleFactory, final InternalTenantContext context) { return new DefaultInvoiceFormatter(config, invoice, locale, currencyConversionApi, bundleFactory, context, currencyLocaleMap); } + + @VisibleForTesting + Map getCurrencyLocaleMap() { + return currencyLocaleMap; + } } diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java index 3f1b7d0e8a..67065b737d 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java @@ -54,12 +54,14 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB { private TranslatorConfig config; private MustacheTemplateEngine templateEngine; + private DefaultInvoiceFormatterFactory defaultInvoiceFormatterFactory; @BeforeClass(groups = "fast") public void beforeClass() throws Exception { super.beforeClass(); config = new ConfigurationObjectFactory(skifeConfigSource).build(TranslatorConfig.class); templateEngine = new MustacheTemplateEngine(); + defaultInvoiceFormatterFactory = new DefaultInvoiceFormatterFactory(); } @Test(groups = "fast") @@ -88,7 +90,7 @@ public void testIgnoreZeroAdjustments() throws Exception { Assert.assertEquals(invoice.getCreditedAmount().doubleValue(), 0.00); // Verify the merge - final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext, getAvailableLocales()); + final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext, defaultInvoiceFormatterFactory.getCurrencyLocaleMap()); final List invoiceItems = formatter.getInvoiceItems(); Assert.assertEquals(invoiceItems.size(), 1); Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED); @@ -142,7 +144,7 @@ public void testMergeItems() throws Exception { Assert.assertEquals(invoice.getRefundedAmount().doubleValue(), -1.00); // Verify the merge - final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext, getAvailableLocales()); + final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext, defaultInvoiceFormatterFactory.getCurrencyLocaleMap()); final List invoiceItems = formatter.getInvoiceItems(); Assert.assertEquals(invoiceItems.size(), 4); Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED); @@ -478,7 +480,7 @@ public void testForDisplay() throws Exception { data.put("text", translator); - data.put("invoice", new DefaultInvoiceFormatter(config, invoice, Locale.US, currencyConversionApi, resourceBundleFactory, internalCallContext, getAvailableLocales())); + data.put("invoice", new DefaultInvoiceFormatter(config, invoice, Locale.US, currencyConversionApi, resourceBundleFactory, internalCallContext, defaultInvoiceFormatterFactory.getCurrencyLocaleMap())); final String formattedText = templateEngine.executeTemplateText(template, data); @@ -487,23 +489,9 @@ public void testForDisplay() throws Exception { private void checkOutput(final Invoice invoice, final String template, final String expected, final Locale locale) { final Map data = new HashMap(); - data.put("invoice", new DefaultInvoiceFormatter(config, invoice, locale, null, resourceBundleFactory, internalCallContext, getAvailableLocales())); + data.put("invoice", new DefaultInvoiceFormatter(config, invoice, locale, null, resourceBundleFactory, internalCallContext, defaultInvoiceFormatterFactory.getCurrencyLocaleMap())); final String formattedText = templateEngine.executeTemplateText(template, data); Assert.assertEquals(formattedText, expected); } - - private Map getAvailableLocales() { - Map currencyLocaleMap = new HashMap(); - - currencyLocaleMap.put(java.util.Currency.getInstance(Locale.US), Locale.US); - currencyLocaleMap.put(java.util.Currency.getInstance(Locale.FRANCE), Locale.FRANCE); - currencyLocaleMap.put(java.util.Currency.getInstance(Locale.JAPAN), Locale.JAPAN); - currencyLocaleMap.put(java.util.Currency.getInstance(Locale.UK), Locale.UK); - Locale brLocale = new Locale("pt", "BR"); - currencyLocaleMap.put(java.util.Currency.getInstance(brLocale), brLocale); - - return currencyLocaleMap; - } - } From abe94d64e4f6a833e179519f0ea621dcc0d9f84b Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 7 Oct 2015 13:47:34 -0700 Subject: [PATCH 125/137] invoice: catch only IllegalArgumentException if the currency isn't known Signed-off-by: Pierre-Alexandre Meyer --- .../template/formatters/DefaultInvoiceFormatter.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java index b12e189241..7ebb8d2a2a 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java @@ -232,19 +232,18 @@ public String getFormattedBalance() { } // Returns the formatted amount with the correct currency symbol that is get from the invoice currency. - private String getFormattedAmountByLocaleAndInvoiceCurrency(BigDecimal amount) { - - String invoiceCurrencyCode = invoice.getCurrency().toString(); - CurrencyUnit currencyUnit = CurrencyUnit.of(invoiceCurrencyCode); + private String getFormattedAmountByLocaleAndInvoiceCurrency(final BigDecimal amount) { + final String invoiceCurrencyCode = invoice.getCurrency().toString(); + final CurrencyUnit currencyUnit = CurrencyUnit.of(invoiceCurrencyCode); final DecimalFormat numberFormatter = (DecimalFormat) DecimalFormat.getCurrencyInstance(locale); - DecimalFormatSymbols dfs = numberFormatter.getDecimalFormatSymbols(); + final DecimalFormatSymbols dfs = numberFormatter.getDecimalFormatSymbols(); dfs.setInternationalCurrencySymbol(currencyUnit.getCurrencyCode()); try { final java.util.Currency currency = java.util.Currency.getInstance(invoiceCurrencyCode); dfs.setCurrencySymbol(currency.getSymbol(currencyLocaleMap.get(currency))); - } catch (Exception e) { + } catch (final IllegalArgumentException e) { dfs.setCurrencySymbol(currencyUnit.getSymbol(locale)); } From fbf57b8265bc2cb143d46061988bf6c990aa5116 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 7 Oct 2015 13:48:24 -0700 Subject: [PATCH 126/137] invoice: remove third digit in euro test Signed-off-by: Pierre-Alexandre Meyer --- .../template/formatters/TestDefaultInvoiceFormatter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java index 67065b737d..b5e5f0377c 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java @@ -158,10 +158,10 @@ public void testMergeItems() throws Exception { } @Test(groups = "fast") - public void testFormattedAmount() throws Exception { + public void testFormattedAmountFranceAndEUR() throws Exception { final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), - new LocalDate(), new BigDecimal("1499.952"), Currency.EUR); + new LocalDate(), new BigDecimal("1499.95"), Currency.EUR); final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR); invoiceEUR.addInvoiceItem(fixedItemEUR); From 5927458b3546d6e0a2cdb6574ce58d318d0e1b7b Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Wed, 7 Oct 2015 13:48:54 -0700 Subject: [PATCH 127/137] invoice: add test with OMR currency Signed-off-by: Pierre-Alexandre Meyer --- .../TestDefaultInvoiceFormatter.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java index b5e5f0377c..32b9b86c19 100644 --- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java +++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java @@ -187,6 +187,36 @@ public void testFormattedAmountFranceAndEUR() throws Exception { Locale.FRANCE); } + @Test(groups = "fast") + public void testFormattedAmountFranceAndOMR() throws Exception { + final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, + UUID.randomUUID().toString(), UUID.randomUUID().toString(), + new LocalDate(), new BigDecimal("1499.958"), Currency.OMR); + final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.OMR); + invoice.addInvoiceItem(fixedItem); + + checkOutput(invoice, + "\n" + + " {{invoice.formattedChargedAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedPaidAmount}}\n" + + "\n" + + "\n" + + " {{invoice.formattedBalance}}\n" + + "", + "\n" + + " 1 499,958 ر.ع.\u200F\n" + + "\n" + + "\n" + + " 0,000 ر.ع.\u200F\n" + + "\n" + + "\n" + + " 1 499,958 ر.ع.\u200F\n" + + "", + Locale.FRANCE); + } + @Test(groups = "fast") public void testFormattedAmountFranceAndJPY() throws Exception { final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null, From 9db9a0d38dd9439f2c4469a724900257264a9ac1 Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Wed, 7 Oct 2015 22:26:19 +0000 Subject: [PATCH 128/137] Update oss pom version to 0.52 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6f2be439e7..472c3bf94c 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.47 + 0.52 killbill 0.15.6-SNAPSHOT From 75b1e027a467ed9b17a715f27d1f7d5659e75df6 Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Wed, 7 Oct 2015 22:57:54 +0000 Subject: [PATCH 129/137] Update NEWS file for 0.15.6 --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 0fd713607b..fc4aaecdb7 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +0.15.6 + See https://github.com/killbill/killbill/releases/tag/killbill-0.15.6 + 0.15.5 Fix with issue with payment combo call where default account fields are not part of the request From e5420a8731d929ccfb0b1a072f54920256076338 Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Wed, 7 Oct 2015 23:11:53 +0000 Subject: [PATCH 130/137] [maven-release-plugin] prepare release killbill-0.15.6 --- account/pom.xml | 2 +- api/pom.xml | 2 +- beatrix/pom.xml | 2 +- catalog/pom.xml | 2 +- currency/pom.xml | 2 +- entitlement/pom.xml | 2 +- invoice/pom.xml | 2 +- jaxrs/pom.xml | 2 +- junction/pom.xml | 2 +- overdue/pom.xml | 2 +- payment/pom.xml | 2 +- pom.xml | 2 +- profiles/killbill/pom.xml | 2 +- profiles/killpay/pom.xml | 2 +- profiles/pom.xml | 2 +- subscription/pom.xml | 2 +- tenant/pom.xml | 2 +- usage/pom.xml | 2 +- util/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/account/pom.xml b/account/pom.xml index 5bb5dc2091..c55f99cbe3 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-account diff --git a/api/pom.xml b/api/pom.xml index 3361c9b190..b650898ddf 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-internal-api diff --git a/beatrix/pom.xml b/beatrix/pom.xml index f5f117e40b..343e4019e0 100644 --- a/beatrix/pom.xml +++ b/beatrix/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-beatrix diff --git a/catalog/pom.xml b/catalog/pom.xml index cea261f679..cebefb6e00 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-catalog diff --git a/currency/pom.xml b/currency/pom.xml index 473298ebf1..664162aafe 100644 --- a/currency/pom.xml +++ b/currency/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-currency diff --git a/entitlement/pom.xml b/entitlement/pom.xml index b64dd693e9..26049fef86 100644 --- a/entitlement/pom.xml +++ b/entitlement/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-entitlement diff --git a/invoice/pom.xml b/invoice/pom.xml index bcf40cb236..c804a8d2ee 100644 --- a/invoice/pom.xml +++ b/invoice/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-invoice diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index 5ba634e6c4..da4d5774af 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-jaxrs diff --git a/junction/pom.xml b/junction/pom.xml index bf62b8995c..e60dd6f318 100644 --- a/junction/pom.xml +++ b/junction/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-junction diff --git a/overdue/pom.xml b/overdue/pom.xml index 9119547532..b30ab6d925 100644 --- a/overdue/pom.xml +++ b/overdue/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-overdue diff --git a/payment/pom.xml b/payment/pom.xml index 0b9df0b938..6d2241866e 100644 --- a/payment/pom.xml +++ b/payment/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-payment diff --git a/pom.xml b/pom.xml index 472c3bf94c..41b5e0df57 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 0.52 killbill - 0.15.6-SNAPSHOT + 0.15.6 pom killbill Library for managing recurring subscriptions and the associated billing diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml index edaf6b1151..615c255300 100644 --- a/profiles/killbill/pom.xml +++ b/profiles/killbill/pom.xml @@ -21,7 +21,7 @@ killbill-profiles org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-profiles-killbill diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml index bead5a09cc..a782775a99 100644 --- a/profiles/killpay/pom.xml +++ b/profiles/killpay/pom.xml @@ -20,7 +20,7 @@ killbill-profiles org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-profiles-killpay diff --git a/profiles/pom.xml b/profiles/pom.xml index e05b9df056..6966b1cd5c 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-profiles diff --git a/subscription/pom.xml b/subscription/pom.xml index 7abe53e34f..5f11141b58 100644 --- a/subscription/pom.xml +++ b/subscription/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-subscription diff --git a/tenant/pom.xml b/tenant/pom.xml index b9ebd09d22..0cb90b4b7f 100644 --- a/tenant/pom.xml +++ b/tenant/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-tenant diff --git a/usage/pom.xml b/usage/pom.xml index b2d4475dce..e5a63884ae 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-usage diff --git a/util/pom.xml b/util/pom.xml index e5acdd9616..335d417da7 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.6-SNAPSHOT + 0.15.6 ../pom.xml killbill-util From 35d802815385d12f4226fe7c6787e519eb9f0bf2 Mon Sep 17 00:00:00 2001 From: Kill Bill core team Date: Wed, 7 Oct 2015 23:11:56 +0000 Subject: [PATCH 131/137] [maven-release-plugin] prepare for next development iteration --- account/pom.xml | 2 +- api/pom.xml | 2 +- beatrix/pom.xml | 2 +- catalog/pom.xml | 2 +- currency/pom.xml | 2 +- entitlement/pom.xml | 2 +- invoice/pom.xml | 2 +- jaxrs/pom.xml | 2 +- junction/pom.xml | 2 +- overdue/pom.xml | 2 +- payment/pom.xml | 2 +- pom.xml | 2 +- profiles/killbill/pom.xml | 2 +- profiles/killpay/pom.xml | 2 +- profiles/pom.xml | 2 +- subscription/pom.xml | 2 +- tenant/pom.xml | 2 +- usage/pom.xml | 2 +- util/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/account/pom.xml b/account/pom.xml index c55f99cbe3..9c9e729160 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-account diff --git a/api/pom.xml b/api/pom.xml index b650898ddf..e28d1d9257 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-internal-api diff --git a/beatrix/pom.xml b/beatrix/pom.xml index 343e4019e0..8a619f0df1 100644 --- a/beatrix/pom.xml +++ b/beatrix/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-beatrix diff --git a/catalog/pom.xml b/catalog/pom.xml index cebefb6e00..ef0cf8a6e3 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-catalog diff --git a/currency/pom.xml b/currency/pom.xml index 664162aafe..c53932f8db 100644 --- a/currency/pom.xml +++ b/currency/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-currency diff --git a/entitlement/pom.xml b/entitlement/pom.xml index 26049fef86..71bb94697b 100644 --- a/entitlement/pom.xml +++ b/entitlement/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-entitlement diff --git a/invoice/pom.xml b/invoice/pom.xml index c804a8d2ee..a8fe578da6 100644 --- a/invoice/pom.xml +++ b/invoice/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-invoice diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index da4d5774af..64d7eccf6e 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-jaxrs diff --git a/junction/pom.xml b/junction/pom.xml index e60dd6f318..c50874ba30 100644 --- a/junction/pom.xml +++ b/junction/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-junction diff --git a/overdue/pom.xml b/overdue/pom.xml index b30ab6d925..c1e0238be9 100644 --- a/overdue/pom.xml +++ b/overdue/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-overdue diff --git a/payment/pom.xml b/payment/pom.xml index 6d2241866e..659de0a497 100644 --- a/payment/pom.xml +++ b/payment/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-payment diff --git a/pom.xml b/pom.xml index 41b5e0df57..8bf3ba3738 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 0.52 killbill - 0.15.6 + 0.15.7-SNAPSHOT pom killbill Library for managing recurring subscriptions and the associated billing diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml index 615c255300..47073ebb39 100644 --- a/profiles/killbill/pom.xml +++ b/profiles/killbill/pom.xml @@ -21,7 +21,7 @@ killbill-profiles org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-profiles-killbill diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml index a782775a99..8377c3f312 100644 --- a/profiles/killpay/pom.xml +++ b/profiles/killpay/pom.xml @@ -20,7 +20,7 @@ killbill-profiles org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-profiles-killpay diff --git a/profiles/pom.xml b/profiles/pom.xml index 6966b1cd5c..71410e80d6 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-profiles diff --git a/subscription/pom.xml b/subscription/pom.xml index 5f11141b58..7eca1bf797 100644 --- a/subscription/pom.xml +++ b/subscription/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-subscription diff --git a/tenant/pom.xml b/tenant/pom.xml index 0cb90b4b7f..516ef5a815 100644 --- a/tenant/pom.xml +++ b/tenant/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-tenant diff --git a/usage/pom.xml b/usage/pom.xml index e5a63884ae..a08114a7af 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -19,7 +19,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-usage diff --git a/util/pom.xml b/util/pom.xml index 335d417da7..961a2559be 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -21,7 +21,7 @@ killbill org.kill-bill.billing - 0.15.6 + 0.15.7-SNAPSHOT ../pom.xml killbill-util From 1f88176a294b6a1be0f595e34d49e4e6449fab9e Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Thu, 8 Oct 2015 11:10:23 -0700 Subject: [PATCH 132/137] payment: remove legacy NoOpPaymentPluginApi Signed-off-by: Pierre-Alexandre Meyer --- .../plugin/api/NoOpPaymentPluginApi.java | 33 -- .../DefaultNoOpPaymentProviderPlugin.java | 289 ------------------ .../ExternalPaymentProviderPlugin.java | 14 +- .../NoOpPaymentProviderPluginModule.java | 42 --- .../NoOpPaymentProviderPluginProvider.java | 62 ---- .../provider/MockPaymentProviderPlugin.java | 9 +- 6 files changed, 7 insertions(+), 442 deletions(-) delete mode 100644 api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java delete mode 100644 payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java delete mode 100644 payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginModule.java delete mode 100644 payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginProvider.java diff --git a/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java b/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java deleted file mode 100644 index 40eee4fc06..0000000000 --- a/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.payment.plugin.api; - -import java.util.List; -import java.util.UUID; - -public interface NoOpPaymentPluginApi extends PaymentPluginApi { - - void clear(); - - void makeNextPaymentFailWithError(); - - void makeNextPaymentFailWithException(); - - void makeAllInvoicesFailWithError(boolean failure); - - void updatePaymentTransactions(UUID paymentId, List newTransactions); -} diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java deleted file mode 100644 index a1943004cd..0000000000 --- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC - * - * The Billing Project licenses this file to you 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 org.killbill.billing.payment.provider; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.killbill.billing.catalog.api.Currency; -import org.killbill.billing.payment.api.PaymentMethodPlugin; -import org.killbill.billing.payment.api.PluginProperty; -import org.killbill.billing.payment.api.TransactionType; -import org.killbill.billing.payment.plugin.api.GatewayNotification; -import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor; -import org.killbill.billing.payment.plugin.api.NoOpPaymentPluginApi; -import org.killbill.billing.payment.plugin.api.PaymentMethodInfoPlugin; -import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; -import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; -import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; -import org.killbill.billing.util.callcontext.CallContext; -import org.killbill.billing.util.callcontext.TenantContext; -import org.killbill.billing.util.entity.DefaultPagination; -import org.killbill.billing.util.entity.Pagination; -import org.killbill.clock.Clock; - -import com.google.common.base.Predicate; -import com.google.common.collect.Collections2; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.inject.Inject; - -public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi { - - private static final String PLUGIN_NAME = "__NO_OP__"; - - private final AtomicBoolean makeNextInvoiceFailWithError = new AtomicBoolean(false); - private final AtomicBoolean makeNextInvoiceFailWithException = new AtomicBoolean(false); - private final AtomicBoolean makeAllInvoicesFailWithError = new AtomicBoolean(false); - - private final Map> payments = new ConcurrentHashMap>(); - private final Map> paymentMethods = new ConcurrentHashMap>(); - - private final Clock clock; - - @Inject - public DefaultNoOpPaymentProviderPlugin(final Clock clock) { - this.clock = clock; - clear(); - } - - @Override - public void clear() { - makeNextInvoiceFailWithException.set(false); - makeAllInvoicesFailWithError.set(false); - makeNextInvoiceFailWithError.set(false); - } - - @Override - public void makeNextPaymentFailWithError() { - makeNextInvoiceFailWithError.set(true); - } - - @Override - public void makeNextPaymentFailWithException() { - makeNextInvoiceFailWithException.set(true); - } - - @Override - public void makeAllInvoicesFailWithError(final boolean failure) { - makeAllInvoicesFailWithError.set(failure); - } - - @Override - public void updatePaymentTransactions(final UUID paymentId, final List newTransactions) { - throw new IllegalStateException("Not implemented"); - } - - @Override - public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) - throws PaymentPluginApiException { - return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency); - } - - @Override - public PaymentTransactionInfoPlugin capturePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) - throws PaymentPluginApiException { - return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency); - } - - @Override - public PaymentTransactionInfoPlugin purchasePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency); - } - - @Override - public PaymentTransactionInfoPlugin voidPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final Iterable properties, final CallContext context) - throws PaymentPluginApiException { - return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null); - } - - @Override - public PaymentTransactionInfoPlugin creditPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) - throws PaymentPluginApiException { - return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency); - } - - @Override - public List getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final Iterable properties, final TenantContext context) throws PaymentPluginApiException { - return payments.get(kbPaymentId.toString()); - } - - @Override - public Pagination searchPayments(final String searchKey, final Long offset, final Long limit, final Iterable properties, final TenantContext tenantContext) throws PaymentPluginApiException { - - final List flattenedTransactions = ImmutableList.copyOf(Iterables.concat(payments.values())); - final Collection filteredTransactions = Collections2.filter(flattenedTransactions, - new Predicate() { - @Override - public boolean apply(final PaymentTransactionInfoPlugin input) { - return (input.getKbPaymentId() != null && input.getKbPaymentId().toString().equals(searchKey)) || - (input.getFirstPaymentReferenceId() != null && input.getFirstPaymentReferenceId().contains(searchKey)) || - (input.getSecondPaymentReferenceId() != null && input.getSecondPaymentReferenceId().contains(searchKey)); - } - } - ); - - final ImmutableList allResults = ImmutableList.copyOf(filteredTransactions); - - final List results; - if (offset >= allResults.size()) { - results = ImmutableList.of(); - } else if (offset + limit > allResults.size()) { - results = allResults.subList(offset.intValue(), allResults.size()); - } else { - results = allResults.subList(offset.intValue(), offset.intValue() + limit.intValue()); - } - - return new DefaultPagination(offset, limit, (long) results.size(), (long) payments.values().size(), results.iterator()); - } - - @Override - public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - final PaymentMethodPlugin realWithID = new DefaultNoOpPaymentMethodPlugin(kbPaymentMethodId, paymentMethodProps); - List pms = paymentMethods.get(kbPaymentMethodId.toString()); - if (pms == null) { - pms = new LinkedList(); - paymentMethods.put(kbPaymentMethodId.toString(), pms); - } - pms.add(realWithID); - } - - @Override - public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - PaymentMethodPlugin toBeDeleted = null; - final List pms = paymentMethods.get(kbPaymentMethodId.toString()); - if (pms != null) { - for (final PaymentMethodPlugin cur : pms) { - if (cur.getExternalPaymentMethodId().equals(kbPaymentMethodId.toString())) { - toBeDeleted = cur; - break; - } - } - } - - if (toBeDeleted != null) { - pms.remove(toBeDeleted); - } - } - - @Override - public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable properties, final TenantContext context) throws PaymentPluginApiException { - final List paymentMethodPlugins = paymentMethods.get(kbPaymentMethodId.toString()); - if (paymentMethodPlugins == null || paymentMethodPlugins.isEmpty()) { - return null; - } else { - return paymentMethodPlugins.get(0); - } - } - - @Override - public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - } - - @Override - public List getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final Iterable properties, final CallContext context) { - return ImmutableList.of(); - } - - @Override - public Pagination searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final Iterable properties, final TenantContext tenantContext) throws PaymentPluginApiException { - final ImmutableList allResults = ImmutableList.copyOf(Iterables.filter(Iterables.concat(paymentMethods.values()), new Predicate() { - @Override - public boolean apply(final PaymentMethodPlugin input) { - return input.getKbPaymentMethodId().toString().equals(searchKey); - } - })); - - final List results; - if (offset >= allResults.size()) { - results = ImmutableList.of(); - } else if (offset + limit > allResults.size()) { - results = allResults.subList(offset.intValue(), allResults.size()); - } else { - results = allResults.subList(offset.intValue(), offset.intValue() + limit.intValue()); - } - - return new DefaultPagination(offset, limit, (long) results.size(), (long) paymentMethods.values().size(), results.iterator()); - } - - @Override - public void resetPaymentMethods(final UUID kbAccountId, final List paymentMethods, final Iterable properties, final CallContext callContext) { - } - - @Override - public HostedPaymentPageFormDescriptor buildFormDescriptor(final UUID kbAccountId, final Iterable customFields, final Iterable properties, final CallContext callContext) { - return null; - } - - @Override - public GatewayNotification processNotification(final String notification, final Iterable properties, final CallContext callContext) throws PaymentPluginApiException { - return null; - } - - @Override - public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal refundAmount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - final List transactions = getPaymentInfo(kbAccountId, kbPaymentId, properties, context); - if (transactions == null || transactions.size() == 0) { - throw new PaymentPluginApiException("", String.format("No payment found for payment id %s (plugin %s)", kbPaymentId.toString(), PLUGIN_NAME)); - } - - final Iterable refundTransactions = Iterables.filter(transactions, new Predicate() { - @Override - public boolean apply(final PaymentTransactionInfoPlugin input) { - return input.getTransactionType() == TransactionType.REFUND; - } - }); - - BigDecimal maxAmountRefundable = BigDecimal.ZERO; - for (PaymentTransactionInfoPlugin cur : refundTransactions) { - maxAmountRefundable = maxAmountRefundable.add(cur.getAmount()); - } - - if (maxAmountRefundable.compareTo(refundAmount) < 0) { - throw new PaymentPluginApiException("", String.format("Refund amount of %s for payment id %s is bigger than the payment amount %s (plugin %s)", - refundAmount, kbPaymentId.toString(), maxAmountRefundable, PLUGIN_NAME)); - } - return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency); - } - - private PaymentTransactionInfoPlugin getInternalNoopPaymentInfoResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType transactionType, final BigDecimal amount, final Currency currency) throws PaymentPluginApiException { - if (makeNextInvoiceFailWithException.getAndSet(false)) { - throw new PaymentPluginApiException("", "test error"); - } - - final PaymentPluginStatus status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED; - BigDecimal totalAmount = amount; - - List paymentTransactionInfoPlugins = payments.get(kbPaymentId.toString()); - if (paymentTransactionInfoPlugins == null) { - paymentTransactionInfoPlugins = new ArrayList(); - payments.put(kbPaymentId.toString(), paymentTransactionInfoPlugins); - } - final PaymentTransactionInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, transactionType, totalAmount, currency, clock.getUTCNow(), clock.getUTCNow(), status, null); - paymentTransactionInfoPlugins.add(result); - return result; - } -} diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java index 788bb2816c..840defd7d5 100644 --- a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java +++ b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java @@ -29,11 +29,11 @@ import org.killbill.billing.payment.api.TransactionType; import org.killbill.billing.payment.plugin.api.GatewayNotification; import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor; -import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; import org.killbill.billing.payment.plugin.api.PaymentMethodInfoPlugin; import org.killbill.billing.payment.plugin.api.PaymentPluginApi; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; +import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.TenantContext; import org.killbill.billing.util.entity.DefaultPagination; @@ -41,14 +41,11 @@ import org.killbill.clock.Clock; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterators; +import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; /** * Special plugin used to record external payments (i.e. payments not issued by Killbill), such as checks. - *

- * The implementation is very similar to the no-op plugin, which it extends. This can potentially be an issue - * if Killbill is processing a lot of external payments as they are all kept in memory. */ public class ExternalPaymentProviderPlugin implements PaymentPluginApi { @@ -88,18 +85,17 @@ public PaymentTransactionInfoPlugin creditPayment(final UUID kbAccountId, final @Override public List getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final Iterable properties, final TenantContext context) throws PaymentPluginApiException { - // TODO broken... return ImmutableList.of(); } @Override public Pagination searchPayments(final String searchKey, final Long offset, final Long limit, final Iterable properties, final TenantContext tenantContext) throws PaymentPluginApiException { - return new DefaultPagination(offset, limit, 0L, 0L, Iterators.emptyIterator()); + return new DefaultPagination(offset, limit, 0L, 0L, ImmutableSet.of().iterator()); } @Override public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal refundAmount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.REFUND, BigDecimal.ZERO, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null); + return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null); } @Override @@ -126,7 +122,7 @@ public List getPaymentMethods(final UUID kbAccountId, f @Override public Pagination searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final Iterable properties, final TenantContext tenantContext) throws PaymentPluginApiException { - return new DefaultPagination(offset, limit, 0L, 0L, Iterators.emptyIterator()); + return new DefaultPagination(offset, limit, 0L, 0L, ImmutableSet.of().iterator()); } @Override diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginModule.java b/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginModule.java deleted file mode 100644 index 9b640c058a..0000000000 --- a/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginModule.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * Copyright 2014 Groupon, Inc - * Copyright 2014 The Billing Project, LLC - * - * The Billing Project licenses this file to you 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 org.killbill.billing.payment.provider; - -import org.killbill.billing.platform.api.KillbillConfigSource; -import org.killbill.billing.util.glue.KillBillModule; - -import com.google.inject.name.Names; - -public class NoOpPaymentProviderPluginModule extends KillBillModule { - - private final String instanceName; - - public NoOpPaymentProviderPluginModule(final String instanceName, final KillbillConfigSource configSource) { - super(configSource); - this.instanceName = instanceName; - } - - @Override - protected void configure() { - bind(DefaultNoOpPaymentProviderPlugin.class) - .annotatedWith(Names.named(instanceName)) - .toProvider(new NoOpPaymentProviderPluginProvider(instanceName)) - .asEagerSingleton(); - } -} diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginProvider.java b/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginProvider.java deleted file mode 100644 index 8b687c01f6..0000000000 --- a/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginProvider.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you 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 org.killbill.billing.payment.provider; - -import com.google.inject.Inject; -import com.google.inject.Provider; - -import org.killbill.billing.osgi.api.OSGIServiceDescriptor; -import org.killbill.billing.osgi.api.OSGIServiceRegistration; -import org.killbill.billing.payment.plugin.api.PaymentPluginApi; -import org.killbill.clock.Clock; - -public class NoOpPaymentProviderPluginProvider implements Provider { - - private final String instanceName; - - private Clock clock; - private OSGIServiceRegistration registry; - - public NoOpPaymentProviderPluginProvider(final String instanceName) { - this.instanceName = instanceName; - - } - - @Inject - public void setPaymentProviderPluginRegistry(final OSGIServiceRegistration registry, final Clock clock) { - this.clock = clock; - this.registry = registry; - } - - @Override - public DefaultNoOpPaymentProviderPlugin get() { - - final DefaultNoOpPaymentProviderPlugin plugin = new DefaultNoOpPaymentProviderPlugin(clock); - final OSGIServiceDescriptor desc = new OSGIServiceDescriptor() { - @Override - public String getPluginSymbolicName() { - return null; - } - @Override - public String getRegistrationName() { - return instanceName; - } - }; - registry.registerService(desc, plugin); - return plugin; - } -} diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java index f3b7088f8f..102097d41e 100644 --- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java +++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java @@ -34,8 +34,8 @@ import org.killbill.billing.payment.api.TransactionType; import org.killbill.billing.payment.plugin.api.GatewayNotification; import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor; -import org.killbill.billing.payment.plugin.api.NoOpPaymentPluginApi; import org.killbill.billing.payment.plugin.api.PaymentMethodInfoPlugin; +import org.killbill.billing.payment.plugin.api.PaymentPluginApi; import org.killbill.billing.payment.plugin.api.PaymentPluginApiException; import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin; @@ -54,7 +54,7 @@ * This MockPaymentProviderPlugin only works for a single accounts as we don't specify the accountId * for operations such as addPaymentMethod. */ -public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi { +public class MockPaymentProviderPlugin implements PaymentPluginApi { public static final String PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE = "paymentPluginStatusOverride"; @@ -182,7 +182,6 @@ public MockPaymentProviderPlugin(final Clock clock) { clear(); } - @Override public void clear() { makeNextInvoiceFailWithException.set(false); makeAllInvoicesFailWithError.set(false); @@ -193,22 +192,18 @@ public void clear() { paymentMethodsInfo.clear(); } - @Override public void makeNextPaymentFailWithError() { makeNextInvoiceFailWithError.set(true); } - @Override public void makeNextPaymentFailWithException() { makeNextInvoiceFailWithException.set(true); } - @Override public void makeAllInvoicesFailWithError(final boolean failure) { makeAllInvoicesFailWithError.set(failure); } - @Override public void updatePaymentTransactions(final UUID paymentId, final List newTransactions) { if (paymentTransactions.containsKey(paymentId.toString())) { paymentTransactions.put (paymentId.toString(), newTransactions); From 93abd2375b06d7a5513441efe36a4df559fc1ab2 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Mon, 12 Oct 2015 09:47:08 -0700 Subject: [PATCH 133/137] profiles: Rely on the config isConfiguredToReturnGZIPResponses method instead of the direct use of the system property org.killbill.server.http.gzip --- .../billing/server/listeners/KillbillGuiceListener.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java index 8e37e121f4..e031a5d507 100644 --- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java +++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java @@ -47,8 +47,6 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener { - private static final String ENABLE_HTTP_GZIP_RESPONSES = "org.killbill.server.http.gzip"; - private static final Logger logger = LoggerFactory.getLogger(KillbillGuiceListener.class); private static final String SWAGGER_PATH = "api-docs"; @@ -76,8 +74,7 @@ protected ServletModule getServletModule() { // In order to use the GZIPContentEncodingFilter, the jersey param "com.sun.jersey.config.feature.logging.DisableEntitylogging" // must not be set to false. - - if (Boolean.valueOf(System.getProperty(ENABLE_HTTP_GZIP_RESPONSES, "false"))) { + if (config.isConfiguredToReturnGZIPResponses()) { logger.info("Enable http gzip responses"); builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName()); } From e96b39ae50b5b12ab64f3aaaebe915ff7dc48d0f Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Mon, 12 Oct 2015 10:35:51 -0700 Subject: [PATCH 134/137] util: Fixes #408 Fixed the CTOR to correctly return the list of applicable object types --- .../util/tag/DefaultTagDefinition.java | 15 +++++- .../util/tag/TestDefaultTagDefinition.java | 49 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 util/src/test/java/org/killbill/billing/util/tag/TestDefaultTagDefinition.java diff --git a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java index 32d85f8edc..48383feefe 100644 --- a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java +++ b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java @@ -44,13 +44,14 @@ public DefaultTagDefinition(final String name, final String description, final B } public DefaultTagDefinition(final UUID id, final String name, final String description, final Boolean isControlTag) { - this(id, name, description, isControlTag, ImmutableList.copyOf(ObjectType.values())); + this(id, name, description, isControlTag, getApplicableObjectTypes(id, isControlTag)); } public DefaultTagDefinition(final ControlTagType controlTag) { this(controlTag.getId(), controlTag.toString(), controlTag.getDescription(), true, controlTag.getApplicableObjectTypes()); } + @JsonCreator public DefaultTagDefinition(@JsonProperty("id") final UUID id, @JsonProperty("name") final String name, @@ -131,4 +132,16 @@ public int hashCode() { result = 31 * result + (applicableObjectTypes != null ? applicableObjectTypes.hashCode() : 0); return result; } + + private static List getApplicableObjectTypes(final UUID id, final Boolean isControlTag) { + if (!isControlTag) { + return ImmutableList.of(); + } + for (final ControlTagType cur : ControlTagType.values()) { + if (cur.getId().equals(id)) { + return cur.getApplicableObjectTypes(); + } + } + throw new IllegalStateException(String.format("ControlTag id %s does not seem to exist", id)); + } } diff --git a/util/src/test/java/org/killbill/billing/util/tag/TestDefaultTagDefinition.java b/util/src/test/java/org/killbill/billing/util/tag/TestDefaultTagDefinition.java new file mode 100644 index 0000000000..59aaf26ae1 --- /dev/null +++ b/util/src/test/java/org/killbill/billing/util/tag/TestDefaultTagDefinition.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014-2015 Groupon, Inc + * Copyright 2014-2015 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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 org.killbill.billing.util.tag; + +import java.util.UUID; + +import org.killbill.billing.util.UtilTestSuiteNoDB; +import org.killbill.billing.util.api.TagApiException; +import org.killbill.billing.util.api.TagDefinitionApiException; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TestDefaultTagDefinition extends UtilTestSuiteNoDB { + + @Test(groups = "fast") + public void testDefaultTagDefinition() throws TagApiException, TagDefinitionApiException { + + final DefaultTagDefinition def1 = new DefaultTagDefinition(UUID.randomUUID(), "foo", "nothing", false); + Assert.assertTrue(def1.getApplicableObjectTypes().isEmpty()); + + for (final ControlTagType cur : ControlTagType.values()) { + + final DefaultTagDefinition curDef = new DefaultTagDefinition(cur.getId(), cur.name(), cur.getDescription(), true); + Assert.assertFalse(curDef.getApplicableObjectTypes().isEmpty()); + Assert.assertEquals(curDef.getApplicableObjectTypes(), cur.getApplicableObjectTypes()); + } + + try { + new DefaultTagDefinition(UUID.randomUUID(), "bar", "nothing again", true); + Assert.fail("Not a control tag type"); + } catch (final IllegalStateException e) { + } + } +} From 24701603fe1cf6857feeb88b64df9ed63392c77e Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Mon, 12 Oct 2015 10:41:19 -0700 Subject: [PATCH 135/137] util: Code review integratiuon for e96b39ae50b5b12ab64f3aaaebe915ff7dc48d0f (return all object types for user tags) --- .../org/killbill/billing/util/tag/DefaultTagDefinition.java | 2 +- .../killbill/billing/util/tag/TestDefaultTagDefinition.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java index 48383feefe..05ca6b49f3 100644 --- a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java +++ b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java @@ -135,7 +135,7 @@ public int hashCode() { private static List getApplicableObjectTypes(final UUID id, final Boolean isControlTag) { if (!isControlTag) { - return ImmutableList.of(); + return ImmutableList.copyOf(ObjectType.values()); } for (final ControlTagType cur : ControlTagType.values()) { if (cur.getId().equals(id)) { diff --git a/util/src/test/java/org/killbill/billing/util/tag/TestDefaultTagDefinition.java b/util/src/test/java/org/killbill/billing/util/tag/TestDefaultTagDefinition.java index 59aaf26ae1..d030b62eac 100644 --- a/util/src/test/java/org/killbill/billing/util/tag/TestDefaultTagDefinition.java +++ b/util/src/test/java/org/killbill/billing/util/tag/TestDefaultTagDefinition.java @@ -19,19 +19,23 @@ import java.util.UUID; +import org.killbill.billing.ObjectType; import org.killbill.billing.util.UtilTestSuiteNoDB; import org.killbill.billing.util.api.TagApiException; import org.killbill.billing.util.api.TagDefinitionApiException; import org.testng.Assert; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableList; + public class TestDefaultTagDefinition extends UtilTestSuiteNoDB { @Test(groups = "fast") public void testDefaultTagDefinition() throws TagApiException, TagDefinitionApiException { final DefaultTagDefinition def1 = new DefaultTagDefinition(UUID.randomUUID(), "foo", "nothing", false); - Assert.assertTrue(def1.getApplicableObjectTypes().isEmpty()); + Assert.assertFalse(def1.getApplicableObjectTypes().isEmpty()); + Assert.assertEquals(ImmutableList.copyOf(ObjectType.values()), def1.getApplicableObjectTypes()); for (final ControlTagType cur : ControlTagType.values()) { From c58de48e1dd000034ab52c9ebf80bab1f89521f4 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Mon, 12 Oct 2015 11:28:48 -0700 Subject: [PATCH 136/137] jaxrs: Fixes #407 Uses the value `InvoicePaymentJson#paymentMethodId` when specified and when the boolean externalPayment is not set. --- .../billing/jaxrs/resources/AccountResource.java | 7 ++++--- .../billing/jaxrs/resources/InvoiceResource.java | 9 +++++++-- .../billing/jaxrs/resources/JaxRsResourceBase.java | 3 +-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java index 241cc16ad7..0bd150295d 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java @@ -676,7 +676,8 @@ public Response payAllInvoices(@PathParam("accountId") final String accountId, final BigDecimal amountToPay = (remainingRequestPayment.compareTo(invoice.getBalance()) >= 0) ? invoice.getBalance() : remainingRequestPayment; if (amountToPay.compareTo(BigDecimal.ZERO) > 0) { - createPurchaseForInvoice(account, invoice.getId(), amountToPay, externalPayment, pluginProperties, callContext); + final UUID paymentMethodId = externalPayment ? null : account.getPaymentMethodId(); + createPurchaseForInvoice(account, invoice.getId(), amountToPay, paymentMethodId, externalPayment, pluginProperties, callContext); } remainingRequestPayment = remainingRequestPayment.subtract(amountToPay); if (remainingRequestPayment.compareTo(BigDecimal.ZERO) == 0) { @@ -730,7 +731,7 @@ public Response createPaymentMethod(final PaymentMethodJson json, final UUID paymentMethodId = paymentApi.addPaymentMethod(account, data.getExternalKey(), data.getPluginName(), isDefault, data.getPluginDetail(), pluginProperties, callContext); if (payAllUnpaidInvoices && unpaidInvoices.size() > 0) { for (final Invoice invoice : unpaidInvoices) { - createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), false, pluginProperties, callContext); + createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), paymentMethodId, false, pluginProperties, callContext); } } return uriBuilder.buildResponse(PaymentMethodResource.class, "getPaymentMethod", paymentMethodId, uriInfo.getBaseUri().toString()); @@ -789,7 +790,7 @@ public Response setDefaultPaymentMethod(@PathParam("accountId") final String acc if (payAllUnpaidInvoices) { final Collection unpaidInvoices = invoiceApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext); for (final Invoice invoice : unpaidInvoices) { - createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), false, pluginProperties, callContext); + createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), account.getPaymentMethodId(), false, pluginProperties, callContext); } } return Response.status(Status.OK).build(); diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java index 7b82b75cba..3994d2bc48 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java @@ -111,6 +111,7 @@ import com.codahale.metrics.annotation.Timed; import com.google.common.base.Function; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -489,7 +490,7 @@ public InvoiceItem apply(final InvoiceItemJson invoiceItemJson) { if (!paidInvoices.contains(externalCharge.getInvoiceId())) { paidInvoices.add(externalCharge.getInvoiceId()); final Invoice invoice = invoiceApi.getInvoice(externalCharge.getInvoiceId(), callContext); - createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), false, pluginProperties, callContext); + createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), account.getPaymentMethodId(), false, pluginProperties, callContext); } } } @@ -600,13 +601,17 @@ public Response createInstantPayment(final InvoicePaymentJson payment, verifyNonNullOrEmpty(payment.getAccountId(), "InvoicePaymentJson accountId needs to be set", payment.getTargetInvoiceId(), "InvoicePaymentJson targetInvoiceId needs to be set", payment.getPurchasedAmount(), "InvoicePaymentJson purchasedAmount needs to be set"); + Preconditions.checkArgument(!externalPayment || payment.getPaymentMethodId() == null, "InvoicePaymentJson should not contain a paymwentMethodId when this is an external payment"); final Iterable pluginProperties = extractPluginProperties(pluginPropertiesString); final CallContext callContext = context.createContext(createdBy, reason, comment, request); final Account account = accountUserApi.getAccountById(UUID.fromString(payment.getAccountId()), callContext); + final UUID paymentMethodId = externalPayment ? null : + (payment.getPaymentMethodId() != null ? UUID.fromString(payment.getPaymentMethodId()) : account.getPaymentMethodId()); + final UUID invoiceId = UUID.fromString(payment.getTargetInvoiceId()); - final Payment result = createPurchaseForInvoice(account, invoiceId, payment.getPurchasedAmount(), externalPayment, pluginProperties, callContext); + final Payment result = createPurchaseForInvoice(account, invoiceId, payment.getPurchasedAmount(), paymentMethodId, externalPayment, pluginProperties, callContext); // STEPH should that live in InvoicePayment instead? return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId()); } diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java index 254570143e..28cd1aa72f 100644 --- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java +++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java @@ -386,7 +386,7 @@ protected Iterable extractPluginProperties(@Nullable final Itera return properties; } - protected Payment createPurchaseForInvoice(final Account account, final UUID invoiceId, final BigDecimal amountToPay, final Boolean externalPayment, final Iterable pluginProperties, final CallContext callContext) throws PaymentApiException { + protected Payment createPurchaseForInvoice(final Account account, final UUID invoiceId, final BigDecimal amountToPay, final UUID paymentMethodId, final Boolean externalPayment, final Iterable pluginProperties, final CallContext callContext) throws PaymentApiException { final List properties = new ArrayList(); final Iterator pluginPropertyIterator = pluginProperties.iterator(); @@ -400,7 +400,6 @@ protected Payment createPurchaseForInvoice(final Account account, final UUID inv invoiceId.toString(), false); properties.add(invoiceProperty); - final UUID paymentMethodId = externalPayment ? null : account.getPaymentMethodId(); return paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, null, amountToPay, account.getCurrency(), paymentExternalKey, transactionExternalKey, properties, createInvoicePaymentControlPluginApiPaymentOptions(externalPayment), callContext); } From 3f89f3e8c2e56522feb3b0c82ae12f18e5cb8232 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Wed, 14 Oct 2015 10:47:43 -0700 Subject: [PATCH 137/137] payment: Modify MockPaymentProviderPluginProvider to set gatewayErrorCode and gatewayError and write a test case to make sure the info is returned when there is a payment failure. --- .../IncompletePaymentTransactionTask.java | 1 + .../core/sm/payments/ChargebackOperation.java | 1 + .../core/sm/payments/PaymentOperation.java | 2 + .../DefaultNoOpPaymentInfoPlugin.java | 18 ++-- .../ExternalPaymentProviderPlugin.java | 12 +-- .../killbill/billing/payment/TestJanitor.java | 2 +- .../provider/MockPaymentProviderPlugin.java | 7 +- .../TestDefaultNoOpPaymentInfoPlugin.java | 6 +- .../killbill/billing/jaxrs/TestPayment.java | 91 +++++++++++++------ 9 files changed, 92 insertions(+), 48 deletions(-) diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java index 8133904bd8..15dc19137a 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java @@ -112,6 +112,7 @@ public Void doIteration() { rehydratedPaymentTransaction.getCreatedDate(), rehydratedPaymentTransaction.getCreatedDate(), PaymentPluginStatus.UNDEFINED, + null, null); PaymentTransactionInfoPlugin paymentTransactionInfoPlugin; try { diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java index 7193ab1e65..a2993a68d0 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java @@ -80,6 +80,7 @@ protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws null, null, status, + null, null); } } diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java index 7d31cbbfb2..c39facf70e 100644 --- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java +++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java @@ -121,6 +121,7 @@ private OperationException convertToUnknownTransactionStatusAndErroredPaymentSta paymentStateContext.getCallContext().getCreatedDate(), paymentStateContext.getCallContext().getCreatedDate(), PaymentPluginStatus.UNDEFINED, + null, null); paymentStateContext.setPaymentTransactionInfoPlugin(paymentInfoPlugin); return new OperationException(e, OperationResult.EXCEPTION); @@ -199,6 +200,7 @@ private OperationResult doOperation() throws PaymentApiException { paymentStateContext.getPaymentTransactionModelDao().getEffectiveDate(), paymentStateContext.getPaymentTransactionModelDao().getCreatedDate(), buildPaymentPluginStatusFromOperationResult(paymentStateContext.getOverridePluginOperationResult()), + null, null); paymentStateContext.setPaymentTransactionInfoPlugin(paymentInfoPlugin); return paymentStateContext.getOverridePluginOperationResult(); diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java index e2b187d96a..2b3ade231f 100644 --- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java +++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java @@ -37,12 +37,13 @@ public class DefaultNoOpPaymentInfoPlugin implements PaymentTransactionInfoPlugi private final DateTime effectiveDate; private final DateTime createdDate; private final PaymentPluginStatus status; - private final String error; + private final String gatewayError; + private final String gatewayErrorCode; private final Currency currency; private final TransactionType transactionType; public DefaultNoOpPaymentInfoPlugin(final UUID kbPaymentId, final UUID kbTransactionPaymentId, final TransactionType transactionType, final BigDecimal amount, final Currency currency, final DateTime effectiveDate, - final DateTime createdDate, final PaymentPluginStatus status, final String error) { + final DateTime createdDate, final PaymentPluginStatus status, final String gatewayErrorCode, final String gatewayError) { this.kbPaymentId = kbPaymentId; this.kbTransactionPaymentId = kbTransactionPaymentId; this.transactionType = transactionType; @@ -50,7 +51,8 @@ public DefaultNoOpPaymentInfoPlugin(final UUID kbPaymentId, final UUID kbTransac this.effectiveDate = effectiveDate; this.createdDate = createdDate; this.status = status; - this.error = error; + this.gatewayErrorCode = gatewayErrorCode; + this.gatewayError = gatewayError; this.currency = currency; } @@ -96,12 +98,12 @@ public DateTime getCreatedDate() { @Override public String getGatewayError() { - return error; + return gatewayError; } @Override public String getGatewayErrorCode() { - return null; + return gatewayErrorCode; } @Override @@ -127,7 +129,7 @@ public String toString() { sb.append(", effectiveDate=").append(effectiveDate); sb.append(", createdDate=").append(createdDate); sb.append(", status=").append(status); - sb.append(", error='").append(error).append('\''); + sb.append(", error='").append(gatewayError).append('\''); sb.append(", currency=").append(currency); sb.append('}'); return sb.toString(); @@ -156,7 +158,7 @@ public boolean equals(final Object o) { if (effectiveDate != null ? effectiveDate.compareTo(that.effectiveDate) != 0 : that.effectiveDate != null) { return false; } - if (error != null ? !error.equals(that.error) : that.error != null) { + if (gatewayError != null ? !gatewayError.equals(that.gatewayError) : that.gatewayError != null) { return false; } if (transactionType != null ? !transactionType.equals(that.transactionType) : that.transactionType != null) { @@ -184,7 +186,7 @@ public int hashCode() { result = 31 * result + (transactionType != null ? transactionType.hashCode() : 0); result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0); result = 31 * result + (status != null ? status.hashCode() : 0); - result = 31 * result + (error != null ? error.hashCode() : 0); + result = 31 * result + (gatewayError != null ? gatewayError.hashCode() : 0); result = 31 * result + (currency != null ? currency.hashCode() : 0); return result; } diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java index 840defd7d5..33f1e3026b 100644 --- a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java +++ b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java @@ -60,27 +60,27 @@ public ExternalPaymentProviderPlugin(final Clock clock) { @Override public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext callContext) throws PaymentPluginApiException { - return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null); + return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null, null); } @Override public PaymentTransactionInfoPlugin capturePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext callContext) throws PaymentPluginApiException { - return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null); + return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null, null); } @Override public PaymentTransactionInfoPlugin purchasePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null); + return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null, null); } @Override public PaymentTransactionInfoPlugin voidPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final Iterable properties, final CallContext callContext) throws PaymentPluginApiException { - return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null); + return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null, null); } @Override public PaymentTransactionInfoPlugin creditPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable properties, final CallContext callContext) throws PaymentPluginApiException { - return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null); + return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null, null); } @Override @@ -95,7 +95,7 @@ public Pagination searchPayments(final String sear @Override public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal refundAmount, final Currency currency, final Iterable properties, final CallContext context) throws PaymentPluginApiException { - return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null); + return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null, null); } @Override diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java index e99ccaa9b4..d81caa8ab2 100644 --- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java +++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java @@ -416,7 +416,7 @@ public void testPendingEntriesThatDontMove() throws PaymentApiException, EventBu // Artificially move the transaction status to PENDING AND update state on the plugin as well final List paymentTransactions = mockPaymentProviderPlugin.getPaymentInfo(account.getId(), payment.getId(), ImmutableList.of(), callContext); final PaymentTransactionInfoPlugin oTx = paymentTransactions.remove(0); - final PaymentTransactionInfoPlugin updatePaymentTransaction = new DefaultNoOpPaymentInfoPlugin(oTx.getKbPaymentId(), oTx.getKbTransactionPaymentId(), oTx.getTransactionType(), oTx.getAmount(), oTx.getCurrency(), oTx.getCreatedDate(), oTx.getCreatedDate(), PaymentPluginStatus.PENDING, null); + final PaymentTransactionInfoPlugin updatePaymentTransaction = new DefaultNoOpPaymentInfoPlugin(oTx.getKbPaymentId(), oTx.getKbTransactionPaymentId(), oTx.getTransactionType(), oTx.getAmount(), oTx.getCurrency(), oTx.getCreatedDate(), oTx.getCreatedDate(), PaymentPluginStatus.PENDING, null, null); paymentTransactions.add(updatePaymentTransaction); mockPaymentProviderPlugin.updatePaymentTransactions(payment.getId(), paymentTransactions); diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java index 102097d41e..f3f94e7bbe 100644 --- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java +++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java @@ -56,6 +56,9 @@ */ public class MockPaymentProviderPlugin implements PaymentPluginApi { + public static final String GATEWAY_ERROR_CODE = "gatewayErrorCode"; + public static final String GATEWAY_ERROR = "gatewayError"; + public static final String PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE = "paymentPluginStatusOverride"; public static final String PLUGIN_NAME = "__NO_OP__"; @@ -351,6 +354,8 @@ public boolean apply(final PluginProperty input) { } else { status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED; } + final String errorCode = status == PaymentPluginStatus.PROCESSED ? "" : GATEWAY_ERROR_CODE; + final String error = status == PaymentPluginStatus.PROCESSED ? "" : GATEWAY_ERROR; InternalPaymentInfo info = payments.get(kbPaymentId.toString()); if (info == null) { @@ -358,7 +363,7 @@ public boolean apply(final PluginProperty input) { payments.put(kbPaymentId.toString(), info); } - final PaymentTransactionInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, type, amount, currency, clock.getUTCNow(), clock.getUTCNow(), status, null); + final PaymentTransactionInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, type, amount, currency, clock.getUTCNow(), clock.getUTCNow(), status, errorCode, error); List existingTransactions = paymentTransactions.get(kbPaymentId.toString()); if (existingTransactions == null) { existingTransactions = new ArrayList(); diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java index c4efb54921..e0a40d7995 100644 --- a/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java +++ b/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java @@ -41,15 +41,15 @@ public void testEquals() throws Exception { final String error = UUID.randomUUID().toString(); final DefaultNoOpPaymentInfoPlugin info = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, Currency.USD, effectiveDate, createdDate, - status, error); + status, error, null); Assert.assertEquals(info, info); final DefaultNoOpPaymentInfoPlugin sameInfo = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, Currency.USD, effectiveDate, createdDate, - status, error); + status, error, null); Assert.assertEquals(sameInfo, info); final DefaultNoOpPaymentInfoPlugin otherInfo = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, Currency.USD, effectiveDate, createdDate, - status, UUID.randomUUID().toString()); + status, UUID.randomUUID().toString(), null); Assert.assertNotEquals(otherInfo, info); } } diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java index 22a5996f03..09d899a00b 100644 --- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java +++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java @@ -32,18 +32,51 @@ import org.killbill.billing.client.model.PaymentTransaction; import org.killbill.billing.client.model.Payments; import org.killbill.billing.client.model.PluginProperty; +import org.killbill.billing.osgi.api.OSGIServiceRegistration; import org.killbill.billing.payment.api.TransactionType; +import org.killbill.billing.payment.plugin.api.PaymentPluginApi; import org.killbill.billing.payment.plugin.api.PaymentPluginStatus; import org.killbill.billing.payment.provider.MockPaymentProviderPlugin; import org.testng.Assert; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; + +import static org.testng.Assert.assertEquals; public class TestPayment extends TestJaxrsBase { + @Inject + protected OSGIServiceRegistration registry; + + private MockPaymentProviderPlugin mockPaymentProviderPlugin; + + @BeforeMethod(groups = "slow") + public void beforeMethod() throws Exception { + super.beforeMethod(); + mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(PLUGIN_NAME); + mockPaymentProviderPlugin.clear(); + } + + @Test(groups = "slow") + public void testWithFailedPayment() throws Exception { + final Account account = createAccountWithDefaultPaymentMethod(); + + mockPaymentProviderPlugin.makeNextPaymentFailWithError(); + + final PaymentTransaction authTransaction = new PaymentTransaction(); + authTransaction.setAmount(BigDecimal.ONE); + authTransaction.setCurrency(account.getCurrency()); + authTransaction.setTransactionType(TransactionType.AUTHORIZE.name()); + final Payment payment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.of(), createdBy, reason, comment); + assertEquals(payment.getTransactions().get(0).getGatewayErrorCode(), MockPaymentProviderPlugin.GATEWAY_ERROR_CODE); + assertEquals(payment.getTransactions().get(0).getGatewayErrorMsg(), MockPaymentProviderPlugin.GATEWAY_ERROR); + } + @Test(groups = "slow") public void testCreateRetrievePayment() throws Exception { final Account account = createAccountWithDefaultPaymentMethod(); @@ -133,7 +166,7 @@ public void testCompletionForSubsequentTransaction() throws Exception { killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment); Assert.fail(); } catch (final KillBillClientException e) { - Assert.assertEquals(e.getMessage(), "PaymentTransactionJson transactionType and externalKey need to be set"); + assertEquals(e.getMessage(), "PaymentTransactionJson transactionType and externalKey need to be set"); } // We cannot complete using just the payment external key as JAX-RS doesn't know which transaction to complete @@ -143,7 +176,7 @@ public void testCompletionForSubsequentTransaction() throws Exception { killBillClient.completePayment(completeTransactionByPaymentExternalKey, pluginProperties, createdBy, reason, comment); Assert.fail(); } catch (final KillBillClientException e) { - Assert.assertEquals(e.getMessage(), "PaymentTransactionJson transactionType and externalKey need to be set"); + assertEquals(e.getMessage(), "PaymentTransactionJson transactionType and externalKey need to be set"); } // Finally, it should work if we specify the payment id and transaction external key @@ -290,15 +323,15 @@ private void verifyComboPayment(final Payment payment, final int nbTransactions, final int paymentNb) throws KillBillClientException { Assert.assertNotNull(payment.getPaymentNumber()); - Assert.assertEquals(payment.getPaymentExternalKey(), paymentExternalKey); - Assert.assertEquals(payment.getAuthAmount().compareTo(authAmount), 0); - Assert.assertEquals(payment.getCapturedAmount().compareTo(capturedAmount), 0); - Assert.assertEquals(payment.getRefundedAmount().compareTo(refundedAmount), 0); - Assert.assertEquals(payment.getTransactions().size(), nbTransactions); + assertEquals(payment.getPaymentExternalKey(), paymentExternalKey); + assertEquals(payment.getAuthAmount().compareTo(authAmount), 0); + assertEquals(payment.getCapturedAmount().compareTo(capturedAmount), 0); + assertEquals(payment.getRefundedAmount().compareTo(refundedAmount), 0); + assertEquals(payment.getTransactions().size(), nbTransactions); final Payments Payments = killBillClient.getPayments(); - Assert.assertEquals(Payments.size(), paymentNb); - Assert.assertEquals(Payments.get(paymentNb - 1), payment); + assertEquals(Payments.size(), paymentNb); + assertEquals(Payments.get(paymentNb - 1), payment); } private void verifyPayment(final Account account, @@ -327,27 +360,27 @@ private void verifyPaymentNoTransaction(final Account account, final BigDecimal refundedAmount, final int nbTransactions, final int paymentNb) throws KillBillClientException { - Assert.assertEquals(payment.getAccountId(), account.getAccountId()); - Assert.assertEquals(payment.getPaymentMethodId(), MoreObjects.firstNonNull(paymentMethodId, account.getPaymentMethodId())); + assertEquals(payment.getAccountId(), account.getAccountId()); + assertEquals(payment.getPaymentMethodId(), MoreObjects.firstNonNull(paymentMethodId, account.getPaymentMethodId())); Assert.assertNotNull(payment.getPaymentId()); Assert.assertNotNull(payment.getPaymentNumber()); - Assert.assertEquals(payment.getPaymentExternalKey(), paymentExternalKey); - Assert.assertEquals(payment.getAuthAmount().compareTo(authAmount), 0); - Assert.assertEquals(payment.getCapturedAmount().compareTo(capturedAmount), 0); - Assert.assertEquals(payment.getRefundedAmount().compareTo(refundedAmount), 0); - Assert.assertEquals(payment.getCurrency(), account.getCurrency()); - Assert.assertEquals(payment.getTransactions().size(), nbTransactions); + assertEquals(payment.getPaymentExternalKey(), paymentExternalKey); + assertEquals(payment.getAuthAmount().compareTo(authAmount), 0); + assertEquals(payment.getCapturedAmount().compareTo(capturedAmount), 0); + assertEquals(payment.getRefundedAmount().compareTo(refundedAmount), 0); + assertEquals(payment.getCurrency(), account.getCurrency()); + assertEquals(payment.getTransactions().size(), nbTransactions); final Payments Payments = killBillClient.getPayments(); - Assert.assertEquals(Payments.size(), paymentNb); - Assert.assertEquals(Payments.get(paymentNb - 1), payment); + assertEquals(Payments.size(), paymentNb); + assertEquals(Payments.get(paymentNb - 1), payment); final Payment retrievedPayment = killBillClient.getPayment(payment.getPaymentId()); - Assert.assertEquals(retrievedPayment, payment); + assertEquals(retrievedPayment, payment); final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(account.getAccountId()); - Assert.assertEquals(paymentsForAccount.size(), paymentNb); - Assert.assertEquals(paymentsForAccount.get(paymentNb - 1), payment); + assertEquals(paymentsForAccount.size(), paymentNb); + assertEquals(paymentsForAccount.get(paymentNb - 1), payment); } private void verifyPaymentTransaction(final Account account, @@ -358,19 +391,19 @@ private void verifyPaymentTransaction(final Account account, @Nullable final BigDecimal amount, final String transactionType, final String transactionStatus) { - Assert.assertEquals(paymentTransaction.getPaymentId(), paymentId); + assertEquals(paymentTransaction.getPaymentId(), paymentId); Assert.assertNotNull(paymentTransaction.getTransactionId()); - Assert.assertEquals(paymentTransaction.getTransactionType(), transactionType); - Assert.assertEquals(paymentTransaction.getStatus(), transactionStatus); + assertEquals(paymentTransaction.getTransactionType(), transactionType); + assertEquals(paymentTransaction.getStatus(), transactionStatus); if (amount == null) { Assert.assertNull(paymentTransaction.getAmount()); Assert.assertNull(paymentTransaction.getCurrency()); } else { - Assert.assertEquals(paymentTransaction.getAmount().compareTo(amount), 0); - Assert.assertEquals(paymentTransaction.getCurrency(), account.getCurrency()); + assertEquals(paymentTransaction.getAmount().compareTo(amount), 0); + assertEquals(paymentTransaction.getCurrency(), account.getCurrency()); } - Assert.assertEquals(paymentTransaction.getTransactionExternalKey(), transactionExternalKey); - Assert.assertEquals(paymentTransaction.getPaymentExternalKey(), paymentExternalKey); + assertEquals(paymentTransaction.getTransactionExternalKey(), transactionExternalKey); + assertEquals(paymentTransaction.getPaymentExternalKey(), paymentExternalKey); } private void verifyPaymentWithPendingRefund(final Account account, final UUID paymentMethodId, final String paymentExternalKey, final String authTransactionExternalKey, final BigDecimal purchaseAmount, final String refundTransactionExternalKey, final Payment refundPayment) throws KillBillClientException {