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 dd4d222..5d95ed7 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
@@ -31,10 +31,12 @@ import org.killbill.automaton.OperationException;
import org.killbill.automaton.State;
import org.killbill.automaton.State.EnteringStateCallback;
import org.killbill.automaton.State.LeavingStateCallback;
+import org.killbill.billing.BillingExceptionBase;
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.PaymentControlApiException;
import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.Payment;
@@ -124,12 +126,15 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
} catch (final OperationException e) {
if (e.getCause() instanceof PaymentApiException) {
throw (PaymentApiException) e.getCause();
- // 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.
+ // If the control plugin tries to pass us back a PaymentApiException we throw it
+ } else if (e.getCause() instanceof PaymentControlApiException && e.getCause().getCause() instanceof PaymentApiException) {
+ throw (PaymentApiException) e.getCause().getCause();
} else if (e.getCause() != null || paymentStateContext.getResult() == null) {
throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.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.
return paymentStateContext.getResult();
}
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 51788f7..f3738f5 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
@@ -29,16 +29,23 @@ import javax.inject.Inject;
import javax.inject.Named;
import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
-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.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.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
@@ -52,12 +59,6 @@ import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler
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.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.util.api.TagUserApi;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -240,7 +241,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
final PluginProperty invoiceProp = getPluginProperty(pluginProperties, PROP_IPCD_INVOICE_ID);
if (invoiceProp == null ||
!(invoiceProp.getValue() instanceof String)) {
- throw new PaymentControlApiException("Need to specify a valid invoiceId in property " + PROP_IPCD_INVOICE_ID);
+ throw new PaymentControlApiException("Failed to retrieve invoiceId: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, String.format("Need to specify a valid invoiceId in property ", PROP_IPCD_INVOICE_ID)));
}
return UUID.fromString((String) invoiceProp.getValue());
}
@@ -257,9 +258,11 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
}
if (paymentControlPluginContext.isApiPayment() && isAborted) {
- throw new PaymentControlApiException("Payment for invoice " + invoice.getId() +
- " aborted : invoice balance is = " + invoice.getBalance() +
- ", requested payment amount is = " + paymentControlPluginContext.getAmount());
+ throw new PaymentControlApiException("Abort purchase call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION,
+ String.format("Payment for invoice %s aborted : invoice balance is = %s, requested payment amount is = %s",
+ invoice.getId(),
+ invoice.getBalance(),
+ paymentControlPluginContext.getAmount())));
} else {
return new DefaultPriorPaymentControlResult(isAborted, requestedAmount);
}
@@ -274,26 +277,28 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(pluginProperties);
if ((paymentControlPluginContext.getAmount() == null || paymentControlPluginContext.getAmount().compareTo(BigDecimal.ZERO) == 0) &&
idWithAmount.size() == 0) {
- throw new PaymentControlApiException("Refund for payment, key = " + paymentControlPluginContext.getPaymentExternalKey() +
- " aborted: requested refund amount is = " + paymentControlPluginContext.getAmount());
+ throw new PaymentControlApiException("Abort refund call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION,
+ String.format("Refund for payment, key = %s, aborted: requested refund amount is = %s",
+ paymentControlPluginContext.getPaymentExternalKey(),
+ paymentControlPluginContext.getAmount())));
}
final PaymentModelDao payment = paymentDao.getPayment(paymentControlPluginContext.getPaymentId(), internalContext);
if (payment == null) {
- throw new PaymentControlApiException();
+ throw new PaymentControlApiException("Unexpected null payment");
}
// This will calculate the upper bound on the refund amount based on the invoice items associated with that payment.
- // Note that we are not checking that other (partial) refund occurred, but if the refund ends up being greater than waht is allowed
+ // Note that we are not checking that other (partial) refund occurred, but if the refund ends up being greater than what is allowed
// the call to the gateway would fail; it would need noce to validate on our side though...
final BigDecimal amountToBeRefunded = computeRefundAmount(payment.getId(), paymentControlPluginContext.getAmount(), idWithAmount, internalContext);
final boolean isAborted = amountToBeRefunded.compareTo(BigDecimal.ZERO) == 0;
if (paymentControlPluginContext.isApiPayment() && isAborted) {
- throw new PaymentControlApiException("Refund for payment " + payment.getId() +
- " aborted : invoice item sum amount is " + amountToBeRefunded +
- ", requested refund amount is = " + paymentControlPluginContext.getAmount());
- } else {
- return new DefaultPriorPaymentControlResult(isAborted, amountToBeRefunded);
+ throw new PaymentControlApiException("Abort refund call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION,
+ String.format("Refund for payment %s aborted : invoice item sum amount is %s, requested refund amount is = %s",
+ payment.getId(),
+ amountToBeRefunded,
+ paymentControlPluginContext.getAmount())));
}
}
@@ -318,24 +323,22 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final InternalTenantContext context)
throws PaymentControlApiException {
- if (invoiceItemIdsWithAmounts.size() == 0) {
- if (specifiedRefundAmount == null || specifiedRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
- throw new PaymentControlApiException("You need to specify positive a refund amount");
+ if (specifiedRefundAmount != null) {
+ if (specifiedRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new PaymentControlApiException("Failed to compute refund:", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "You need to specify positive a refund amount"));
}
return specifiedRefundAmount;
}
- final List<InvoiceItem> items;
try {
- items = invoiceApi.getInvoiceForPaymentId(paymentId, context).getInvoiceItems();
-
+ final List<InvoiceItem> items = invoiceApi.getInvoiceForPaymentId(paymentId, context).getInvoiceItems();
BigDecimal amountFromItems = BigDecimal.ZERO;
for (final UUID itemId : invoiceItemIdsWithAmounts.keySet()) {
final BigDecimal specifiedItemAmount = invoiceItemIdsWithAmounts.get(itemId);
final BigDecimal itemAmount = getAmountFromItem(items, itemId);
if (specifiedItemAmount != null &&
(specifiedItemAmount.compareTo(BigDecimal.ZERO) <= 0 || specifiedItemAmount.compareTo(itemAmount) > 0)) {
- throw new PaymentControlApiException("You need to specify valid invoice item amount ");
+ throw new PaymentControlApiException("Failed to compute refund:", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "You need to specify valid invoice item amount "));
}
amountFromItems = amountFromItems.add(Objects.firstNonNull(specifiedItemAmount, itemAmount));
}
@@ -351,7 +354,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
return item.getAmount();
}
}
- throw new PaymentControlApiException("Unable to find invoice item for id " + itemId);
+ throw new PaymentControlApiException(String.format("Unable to find invoice item for id %s", itemId), new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "Invalid plugin properties"));
}
private DateTime computeNextRetryDate(final String paymentExternalKey, final boolean isApiAPayment, final InternalCallContext internalContext) {