killbill-memoizeit
Changes
payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java 16(+8 -8)
payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java 16(+8 -8)
payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java 26(+24 -2)
pom.xml 2(+1 -1)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
index 81d7a94..b501af7 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
@@ -116,6 +116,20 @@ public class TestPaymentRefund extends TestIntegrationBase {
}
}
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/255",
+ expectedExceptions = PaymentApiException.class, expectedExceptionsMessageRegExp = "Payment method .* does not exist")
+ public void testRefundWithDeletedPaymentMethod() throws Exception {
+
+ // delete payment method
+ busHandler.pushExpectedEvent(NextEvent.TAG);
+ paymentApi.deletePaymentMethod(account, account.getPaymentMethodId(), true, true, new ArrayList<PluginProperty>(), callContext);
+ assertListenerStatus();
+
+ // try to create a refund for a payment with its payment method deleted
+ paymentApi.createRefund(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(),
+ UUID.randomUUID().toString(), PLUGIN_PROPERTIES, callContext);
+ }
+
private void setupRefundTest() throws Exception {
final int billingDay = 31;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
index 8c14531..ff2ed5a 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
@@ -19,7 +19,9 @@
package org.killbill.billing.jaxrs.resources;
import java.math.BigDecimal;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -55,6 +57,7 @@ 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.UUIDs;
import org.killbill.billing.util.api.AuditUserApi;
@@ -70,6 +73,7 @@ import org.killbill.clock.Clock;
import org.killbill.commons.metrics.TimedResource;
import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
@@ -145,6 +149,8 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
@ApiResponse(code = 404, message = "Account or payment not found")})
public Response createRefundWithAdjustments(final InvoicePaymentTransactionJson json,
@PathParam("paymentId") final String paymentId,
+ @QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
+ @QueryParam(QUERY_PAYMENT_METHOD_ID) @DefaultValue("") final String paymentMethodId,
@QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@HeaderParam(HDR_REASON) final String reason,
@@ -160,6 +166,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
final Iterable<PluginProperty> pluginProperties;
final String transactionExternalKey = json.getTransactionExternalKey() != null ? json.getTransactionExternalKey() : UUIDs.randomUUID().toString();
+ final String paymentExternalKey = json.getPaymentExternalKey() != null ? json.getPaymentExternalKey() : UUIDs.randomUUID().toString();
if (json.isAdjusted() != null && json.isAdjusted()) {
if (json.getAdjustments() != null && json.getAdjustments().size() > 0) {
final Map<UUID, BigDecimal> adjustments = new HashMap<UUID, BigDecimal>();
@@ -177,8 +184,22 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
pluginProperties = extractPluginProperties(pluginPropertiesString);
}
- final Payment result = paymentApi.createRefundWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(), transactionExternalKey,
- pluginProperties, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+ final Payment result;
+ if (externalPayment) {
+ UUID externalPaymentMethodId = Strings.isNullOrEmpty(paymentMethodId) ? null : UUID.fromString(paymentMethodId);
+
+ final Collection<PluginProperty> pluginPropertiesForExternalRefund = new LinkedList<PluginProperty>();
+ Iterables.addAll(pluginPropertiesForExternalRefund, pluginProperties);
+ pluginPropertiesForExternalRefund.add(new PluginProperty("IPCD_PAYMENT_ID", paymentUuid, false));
+
+ result = paymentApi.createCreditWithPaymentControl(account, externalPaymentMethodId, null, json.getAmount(), account.getCurrency(),
+ paymentExternalKey, transactionExternalKey, pluginPropertiesForExternalRefund,
+ createInvoicePaymentControlPluginApiPaymentOptions(true), callContext);
+ } else {
+ result = paymentApi.createRefundWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(), transactionExternalKey,
+ pluginProperties, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+ }
+
return uriBuilder.buildResponse(InvoicePaymentResource.class, "getInvoicePayment", result.getId(), uriInfo.getBaseUri().toString());
}
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 a5f5ab1..ba275b6 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
@@ -648,7 +648,7 @@ public class InvoiceResource extends JaxRsResourceBase {
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");
+ Preconditions.checkArgument(!externalPayment || payment.getPaymentMethodId() == null, "InvoicePaymentJson should not contain a paymentMethodId when this is an external payment");
final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
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 ac0702a..bb40321 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
@@ -299,7 +299,11 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, "paymentMethodId", "should not be null");
}
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- final UUID nonNulPaymentMethodId = (paymentMethodId != null) ?
+
+ // TODO validate if the code is located properly here
+ // The code should understand that the external payment method needs to be created
+ // if it doesn't exist yet and trigger a CREDIT using the (built-in) external payment plugin.
+ final UUID nonNullPaymentMethodId = (paymentMethodId != null) ?
paymentMethodId :
paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
@@ -310,7 +314,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
try {
logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
- payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
properties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -498,7 +502,6 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
- checkNotNullParameter(paymentMethodId, "paymentMethodId");
if (paymentId == null) {
checkNotNullParameter(amount, "amount");
checkNotNullParameter(currency, "currency");
@@ -512,7 +515,11 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ final UUID nonNullPaymentMethodId = (paymentMethodId != null) ?
+ paymentMethodId :
+ paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+
+ payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -544,7 +551,6 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
checkNotNullParameter(account, "account");
- checkNotNullParameter(paymentMethodId, "paymentMethodId");
if (paymentId == null) {
checkNotNullParameter(amount, "amount");
checkNotNullParameter(currency, "currency");
@@ -558,7 +564,15 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+
+ // TODO validate if the code is located properly here
+ // The code should understand that the external payment method needs to be created
+ // if it doesn't exist yet and trigger a CREDIT using the (built-in) external payment plugin.
+ final UUID nonNullPaymentMethodId = (paymentMethodId != null) ?
+ paymentMethodId :
+ paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+
+ payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
properties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
index fa55ff0..c546b0c 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
@@ -149,13 +149,14 @@ public class PaymentAutomatonDAOHelper {
paymentStateContext.setPaymentTransactionModelDao(paymentDao.getPaymentTransaction(paymentStateContext.getPaymentTransactionModelDao().getId(), internalCallContext));
}
- public String getPaymentProviderPluginName() throws PaymentApiException {
+ public String getPaymentProviderPluginName(final boolean includeDeteled) throws PaymentApiException {
if (pluginName != null) {
return pluginName;
}
final UUID paymentMethodId = paymentStateContext.getPaymentMethodId();
- final PaymentMethodModelDao methodDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, internalCallContext);
+ final PaymentMethodModelDao methodDao = (includeDeteled) ? paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, internalCallContext) :
+ paymentDao.getPaymentMethod(paymentMethodId, internalCallContext);
if (methodDao == null) {
throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
}
@@ -164,7 +165,7 @@ public class PaymentAutomatonDAOHelper {
}
public PaymentPluginApi getPaymentPluginApi() throws PaymentApiException {
- final String pluginName = getPaymentProviderPluginName();
+ final String pluginName = getPaymentProviderPluginName(false);
return getPaymentPluginApi(pluginName);
}
@@ -228,4 +229,8 @@ public class PaymentAutomatonDAOHelper {
gatewayErrorCode,
gatewayErrorMsg);
}
+
+ public String getPluginName() {
+ return pluginName;
+ }
}
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 012d7c0..c1304a7 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
@@ -128,6 +128,7 @@ public class PaymentAutomatonRunner {
final Iterable<PluginProperty> properties,
final CallContext callContext,
final InternalCallContext internalCallContext) throws PaymentApiException {
+
// Retrieve the payment id from the payment external key if needed
final UUID effectivePaymentId = paymentId != null ? paymentId : retrievePaymentId(paymentExternalKey, paymentTransactionExternalKey, internalCallContext);
@@ -165,6 +166,7 @@ public class PaymentAutomatonRunner {
final OperationCallback operationCallback;
final LeavingStateCallback leavingStateCallback;
final EnteringStateCallback enteringStateCallback;
+ Boolean includeDeletedPaymentMethod = Boolean.FALSE;
switch (transactionType) {
case PURCHASE:
operationCallback = new PurchaseOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
@@ -200,12 +202,13 @@ public class PaymentAutomatonRunner {
operationCallback = new ChargebackOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
leavingStateCallback = new ChargebackInitiated(daoHelper, paymentStateContext);
enteringStateCallback = new ChargebackCompleted(daoHelper, paymentStateContext);
+ includeDeletedPaymentMethod = Boolean.TRUE;
break;
default:
throw new IllegalStateException("Unsupported transaction type " + transactionType);
}
- runStateMachineOperation(currentStateName, transactionType, leavingStateCallback, operationCallback, enteringStateCallback, paymentStateContext, daoHelper);
+ runStateMachineOperation(currentStateName, transactionType, leavingStateCallback, operationCallback, enteringStateCallback, includeDeletedPaymentMethod, paymentStateContext, daoHelper);
return paymentStateContext.getPaymentId();
}
@@ -226,10 +229,11 @@ public class PaymentAutomatonRunner {
final LeavingStateCallback leavingStateCallback,
final OperationCallback operationCallback,
final EnteringStateCallback enteringStateCallback,
+ final Boolean includeDeletedPaymentMethod,
final PaymentStateContext paymentStateContext,
final PaymentAutomatonDAOHelper daoHelper) throws PaymentApiException {
try {
- final StateMachineConfig stateMachineConfig = paymentSMHelper.getStateMachineConfig(daoHelper.getPaymentProviderPluginName(), paymentStateContext.getInternalCallContext());
+ final StateMachineConfig stateMachineConfig = paymentSMHelper.getStateMachineConfig(daoHelper.getPaymentProviderPluginName(includeDeletedPaymentMethod), paymentStateContext.getInternalCallContext());
final StateMachine initialStateMachine = stateMachineConfig.getStateMachineForState(initialStateName);
final State initialState = initialStateMachine.getState(initialStateName);
final Operation operation = paymentSMHelper.getOperationForTransaction(stateMachineConfig, transactionType);
@@ -284,4 +288,5 @@ public class PaymentAutomatonRunner {
return paymentIdCandidate;
}
+
}
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 fdbe229..d8cf528 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
@@ -44,13 +44,13 @@ public class AuthorizeOperation extends PaymentOperation {
@Override
protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
logger.debug("Starting AUTHORIZE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
- return plugin.authorizePayment(paymentStateContext.getAccount().getId(),
- paymentStateContext.getPaymentId(),
- paymentStateContext.getTransactionId(),
- paymentStateContext.getPaymentMethodId(),
- paymentStateContext.getAmount(),
- paymentStateContext.getCurrency(),
- paymentStateContext.getProperties(),
- paymentStateContext.getCallContext());
+ return paymentPluginApi.authorizePayment(paymentStateContext.getAccount().getId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getTransactionId(),
+ paymentStateContext.getPaymentMethodId(),
+ paymentStateContext.getAmount(),
+ paymentStateContext.getCurrency(),
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
}
}
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 cbafd42..6f4440d 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
@@ -44,13 +44,13 @@ public class CaptureOperation extends PaymentOperation {
@Override
protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
logger.debug("Starting CAPTURE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
- return plugin.capturePayment(paymentStateContext.getAccount().getId(),
- paymentStateContext.getPaymentId(),
- paymentStateContext.getTransactionId(),
- paymentStateContext.getPaymentMethodId(),
- paymentStateContext.getAmount(),
- paymentStateContext.getCurrency(),
- paymentStateContext.getProperties(),
- paymentStateContext.getCallContext());
+ return paymentPluginApi.capturePayment(paymentStateContext.getAccount().getId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getTransactionId(),
+ paymentStateContext.getPaymentMethodId(),
+ paymentStateContext.getAmount(),
+ paymentStateContext.getCurrency(),
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
}
}
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 2d26268..f98908e 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
@@ -44,13 +44,13 @@ public class CreditOperation extends PaymentOperation {
@Override
protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
logger.debug("Starting CREDIT for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
- return plugin.creditPayment(paymentStateContext.getAccount().getId(),
- paymentStateContext.getPaymentId(),
- paymentStateContext.getTransactionId(),
- paymentStateContext.getPaymentMethodId(),
- paymentStateContext.getAmount(),
- paymentStateContext.getCurrency(),
- paymentStateContext.getProperties(),
- paymentStateContext.getCallContext());
+ return paymentPluginApi.creditPayment(paymentStateContext.getAccount().getId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getTransactionId(),
+ paymentStateContext.getPaymentMethodId(),
+ paymentStateContext.getAmount(),
+ paymentStateContext.getCurrency(),
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
}
}
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 d6a1e7d..cd77f0c 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
@@ -55,7 +55,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
private final Logger logger = LoggerFactory.getLogger(PaymentOperation.class);
protected final PaymentAutomatonDAOHelper daoHelper;
- protected PaymentPluginApi plugin;
+ protected PaymentPluginApi paymentPluginApi;
protected PaymentOperation(final GlobalLocker locker,
final PaymentAutomatonDAOHelper daoHelper,
@@ -68,17 +68,15 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
@Override
public OperationResult doOperationCallback() throws OperationException {
- final String pluginName;
try {
- pluginName = daoHelper.getPaymentProviderPluginName();
- this.plugin = daoHelper.getPaymentPluginApi();
+ this.paymentPluginApi = daoHelper.getPaymentPluginApi();
} catch (final PaymentApiException e) {
throw convertToUnknownTransactionStatusAndErroredPaymentState(e);
}
if (paymentStateContext.shouldLockAccountAndDispatch()) {
// This will already call unwrapExceptionFromDispatchedTask
- return doOperationCallbackWithDispatchAndAccountLock(pluginName);
+ return doOperationCallbackWithDispatchAndAccountLock(daoHelper.getPluginName());
} else {
try {
return 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 7fab5e1..aff762f 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
@@ -44,13 +44,13 @@ public class PurchaseOperation extends PaymentOperation {
@Override
protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
logger.debug("Starting PURCHASE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
- return plugin.purchasePayment(paymentStateContext.getAccount().getId(),
- paymentStateContext.getPaymentId(),
- paymentStateContext.getTransactionId(),
- paymentStateContext.getPaymentMethodId(),
- paymentStateContext.getAmount(),
- paymentStateContext.getCurrency(),
- paymentStateContext.getProperties(),
- paymentStateContext.getCallContext());
+ return paymentPluginApi.purchasePayment(paymentStateContext.getAccount().getId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getTransactionId(),
+ paymentStateContext.getPaymentMethodId(),
+ paymentStateContext.getAmount(),
+ paymentStateContext.getCurrency(),
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
}
}
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 b236ac0..1d86abb 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
@@ -44,13 +44,13 @@ public class RefundOperation extends PaymentOperation {
@Override
protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
logger.debug("Starting REFUND for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
- return plugin.refundPayment(paymentStateContext.getAccount().getId(),
- paymentStateContext.getPaymentId(),
- paymentStateContext.getTransactionId(),
- paymentStateContext.getPaymentMethodId(),
- paymentStateContext.getAmount(),
- paymentStateContext.getCurrency(),
- paymentStateContext.getProperties(),
- paymentStateContext.getCallContext());
+ return paymentPluginApi.refundPayment(paymentStateContext.getAccount().getId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getTransactionId(),
+ paymentStateContext.getPaymentMethodId(),
+ paymentStateContext.getAmount(),
+ paymentStateContext.getCurrency(),
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
}
}
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 b6542d9..c932c59 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
@@ -44,11 +44,11 @@ public class VoidOperation extends PaymentOperation {
@Override
protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
logger.debug("Starting VOID for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
- return plugin.voidPayment(paymentStateContext.getAccount().getId(),
- paymentStateContext.getPaymentId(),
- paymentStateContext.getTransactionId(),
- paymentStateContext.getPaymentMethodId(),
- paymentStateContext.getProperties(),
- paymentStateContext.getCallContext());
+ return paymentPluginApi.voidPayment(paymentStateContext.getAccount().getId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getTransactionId(),
+ paymentStateContext.getPaymentMethodId(),
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
}
}
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 912d736..b8bc30f 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
@@ -93,6 +93,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
public static final String PROP_IPCD_INVOICE_ID = "IPCD_INVOICE_ID";
public static final String PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY = "IPCD_REFUND_IDS_AMOUNTS";
public static final String PROP_IPCD_REFUND_WITH_ADJUSTMENTS = "IPCD_REFUND_WITH_ADJUSTMENTS";
+ public static final String PROP_IPCD_PAYMENT_ID = "IPCD_PAYMENT_ID";
private final PaymentConfig paymentConfig;
private final InvoiceInternalApi invoiceApi;
@@ -131,7 +132,8 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
Preconditions.checkArgument(paymentControlContext.getPaymentApiType() == PaymentApiType.PAYMENT_TRANSACTION);
Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
transactionType == TransactionType.REFUND ||
- transactionType == TransactionType.CHARGEBACK);
+ transactionType == TransactionType.CHARGEBACK ||
+ transactionType == TransactionType.CREDIT);
final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
switch (transactionType) {
@@ -141,6 +143,8 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
return getPluginRefundResult(paymentControlContext, pluginProperties, internalContext);
case CHARGEBACK:
return new DefaultPriorPaymentControlResult(false, paymentControlContext.getAmount());
+ case CREDIT:
+ return getPluginCreditResult(paymentControlContext, pluginProperties, internalContext);
default:
throw new IllegalStateException("Unexpected transactionType " + transactionType);
}
@@ -151,7 +155,8 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
final TransactionType transactionType = paymentControlContext.getTransactionType();
Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
transactionType == TransactionType.REFUND ||
- transactionType == TransactionType.CHARGEBACK);
+ transactionType == TransactionType.CHARGEBACK ||
+ transactionType == TransactionType.CREDIT);
final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
try {
@@ -216,6 +221,17 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
}
break;
+ case CREDIT:
+ final Map<UUID, BigDecimal> idWithAmountMap = extractIdsWithAmountFromProperties(pluginProperties);
+ final PluginProperty properties = getPluginProperty(pluginProperties, PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
+ final boolean isInvoiceAdjusted = properties != null ? Boolean.valueOf((String) properties.getValue()) : false;
+
+ final PluginProperty legacyPayment = getPluginProperty(pluginProperties, PROP_IPCD_PAYMENT_ID);
+ final UUID paymentId = legacyPayment != null ? (UUID) legacyPayment.getValue() : paymentControlContext.getPaymentId();
+
+ invoiceApi.recordRefund(paymentId, paymentControlContext.getAmount(), isInvoiceAdjusted, idWithAmountMap, paymentControlContext.getTransactionExternalKey(), internalContext);
+ break;
+
default:
throw new IllegalStateException("Unexpected transactionType " + transactionType);
}
@@ -253,6 +269,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
break;
+ case CREDIT:
case REFUND:
// We don't retry REFUND
break;
@@ -401,6 +418,11 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
return new DefaultPriorPaymentControlResult(isAborted, amountToBeRefunded);
}
+ private PriorPaymentControlResult getPluginCreditResult(final PaymentControlContext paymentControlPluginContext, final Iterable<PluginProperty> pluginProperties, final InternalCallContext internalContext) throws PaymentControlApiException {
+ // TODO implement
+ return new DefaultPriorPaymentControlResult(false, paymentControlPluginContext.getAmount());
+ }
+
private Map<UUID, BigDecimal> extractIdsWithAmountFromProperties(final Iterable<PluginProperty> properties) {
final PluginProperty prop = getPluginProperty(properties, PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY);
if (prop == null) {
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 a6f0f73..cc32c53 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
@@ -123,7 +123,7 @@ public class TestPaymentOperation extends PaymentTestSuiteNoDB {
final PaymentMethodModelDao paymentMethodModelDao = new PaymentMethodModelDao(paymentStateContext.getPaymentMethodId(), UUID.randomUUID().toString(), clock.getUTCNow(), clock.getUTCNow(),
paymentStateContext.getAccount().getId(), MockPaymentProviderPlugin.PLUGIN_NAME, true);
final PaymentDao paymentDao = Mockito.mock(PaymentDao.class);
- Mockito.when(paymentDao.getPaymentMethodIncludedDeleted(paymentStateContext.getPaymentMethodId(), internalCallContext)).thenReturn(paymentMethodModelDao);
+ Mockito.when(paymentDao.getPaymentMethod(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, paymentConfig, paymentStateContext);
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 1bb43b2..0927ec9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.128</version>
+ <version>0.129-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.17.5-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
index 4d31f12..8672065 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
@@ -36,7 +36,12 @@ import org.killbill.billing.client.model.PaymentMethod;
import org.killbill.billing.client.model.PaymentMethodPluginDetail;
import org.killbill.billing.client.model.PluginProperty;
import org.killbill.billing.client.model.Subscription;
+import org.killbill.billing.client.model.Tags;
+import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
+import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.tag.ControlTagType;
+import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedDB {
@@ -111,6 +116,16 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
return killBillClient.getAccount(input.getExternalKey());
}
+ protected Account createAccountWithExternalPaymentMethod() throws Exception {
+ final Account input = createAccount();
+
+ final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
+ final PaymentMethod paymentMethodJson = new PaymentMethod(null, UUIDs.randomUUID().toString(), input.getAccountId(),
+ true, ExternalPaymentProviderPlugin.PLUGIN_NAME, info);
+ killBillClient.createPaymentMethod(paymentMethodJson, requestOptions);
+ return killBillClient.getAccount(input.getExternalKey(), requestOptions);
+ }
+
protected Account createAccount() throws Exception {
return createAccount(null);
}
@@ -147,6 +162,24 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
return accountJson;
}
+ protected Account createAccountWithExternalPMBundleAndSubscriptionAndManualPayTagAndWaitForFirstInvoice() throws Exception {
+ final Account accountJson = createAccountWithExternalPaymentMethod();
+ assertNotNull(accountJson);
+
+ final Tags accountTag = killBillClient.createAccountTag(accountJson.getAccountId(), ControlTagType.MANUAL_PAY.getId(), requestOptions);
+ assertNotNull(accountTag);
+ assertEquals(accountTag.get(0).getTagDefinitionId(), ControlTagType.MANUAL_PAY.getId());
+
+ // Add a bundle, subscription and move the clock to get the first invoice
+ final Subscription subscriptionJson = createEntitlement(accountJson.getAccountId(), UUID.randomUUID().toString(), "Shotgun",
+ ProductCategory.BASE, BillingPeriod.MONTHLY, true);
+ assertNotNull(subscriptionJson);
+ clock.addDays(32);
+ crappyWaitForLackOfProperSynchonization();
+
+ return accountJson;
+ }
+
protected Account createAccountNoPMBundleAndSubscription() throws Exception {
// Create an account with no payment method
final Account accountJson = createAccount();
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExternalRefund.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExternalRefund.java
new file mode 100644
index 0000000..80575fd
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExternalRefund.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.Invoice;
+import org.killbill.billing.client.model.InvoiceItem;
+import org.killbill.billing.client.model.InvoicePayment;
+import org.killbill.billing.client.model.InvoicePaymentTransaction;
+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.client.model.PaymentMethodPluginDetail;
+import org.killbill.billing.client.model.Payments;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.payment.api.TransactionType;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestExternalRefund extends TestJaxrsBase {
+
+ @Test(groups = "slow", description = "#255 - Scenario 0 - Can refund an automatic payment. This is a test to validate the correct behaviour.")
+ public void testAutomaticPaymentAndRefund() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+ final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+ Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, true, requestOptions);
+ final List<InvoiceItem> itemsToBeAdjusted = invoices.get(1).getItems();
+
+ // regular refund
+ final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+ invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+ invoicePaymentTransactionRequest.setCurrency(accountJson.getCurrency().toString());
+ invoicePaymentTransactionRequest.setPaymentId(payment.getPaymentId());
+ final InvoicePayment invoicePaymentRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, requestOptions);
+ assertNotNull(invoicePaymentRefund);
+
+ assertSingleInvoicePaymentRefund(invoicePaymentRefund);
+ assertRefundInvoiceNoAdjustments(accountJson.getAccountId());
+ assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.valueOf(249.95), BigDecimal.ZERO);
+ }
+
+ @Test(groups = "slow", description = "#255 - Scenario 0 - Can refund an automatic payment over item adjustments. This is a test to validate the correct behaviour.")
+ public void testAutomaticPaymentAndRefundWithAdjustments() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+ final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+ Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, true, requestOptions);
+ final List<InvoiceItem> itemsToBeAdjusted = invoices.get(1).getItems();
+
+ // regular refund
+ final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+ invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+ invoicePaymentTransactionRequest.setCurrency(accountJson.getCurrency().toString());
+ invoicePaymentTransactionRequest.setPaymentId(payment.getPaymentId());
+ invoicePaymentTransactionRequest.setIsAdjusted(true);
+ invoicePaymentTransactionRequest.setAdjustments(itemsToBeAdjusted);
+ final InvoicePayment invoicePaymentRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, requestOptions);
+ assertNotNull(invoicePaymentRefund);
+
+ assertSingleInvoicePaymentRefund(invoicePaymentRefund);
+ assertRefundInvoiceAdjustments(accountJson.getAccountId());
+ assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.ZERO, BigDecimal.ZERO);
+ }
+
+ @Test(groups = "slow", description = "#255 - Scenario 1 - Can refund a manual payment though an external refund")
+ public void testManualPaymentAndExternalRefund() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithExternalPMBundleAndSubscriptionAndManualPayTagAndWaitForFirstInvoice();
+
+ final Invoices invoicesForAccount = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+ final Invoice unpaidInvoice = invoicesForAccount.get(1);
+ assertEquals(unpaidInvoice.getBalance().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+ final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ assertEquals(paymentsForAccount.size(), 0);
+
+ final InvoicePayment invoicePaymentRequest = new InvoicePayment();
+ invoicePaymentRequest.setTargetInvoiceId(unpaidInvoice.getInvoiceId());
+ invoicePaymentRequest.setAccountId(accountJson.getAccountId());
+ invoicePaymentRequest.setCurrency(unpaidInvoice.getCurrency().toString());
+ invoicePaymentRequest.setPurchasedAmount(unpaidInvoice.getAmount());
+ final InvoicePayment invoicePayment = killBillClient.createInvoicePayment(invoicePaymentRequest, true, requestOptions);
+ assertEquals(invoicePayment.getPurchasedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ assertEquals(invoicePayment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+
+ final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+ invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+ invoicePaymentTransactionRequest.setPaymentId(invoicePayment.getPaymentId());
+ final InvoicePayment invoicePaymentRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, requestOptions);
+ assertNotNull(invoicePaymentRefund);
+
+ assertSingleInvoicePaymentRefund(invoicePaymentRefund);
+ assertRefundInvoiceNoAdjustments(accountJson.getAccountId());
+ assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.valueOf(249.95), BigDecimal.ZERO);
+ }
+
+ @Test(groups = "slow", description = "#255 - Scenario 1 - Can refund a manual payment though an external refund over item adjustments")
+ public void testManualPaymentAndExternalRefundWithAdjustments() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithExternalPMBundleAndSubscriptionAndManualPayTagAndWaitForFirstInvoice();
+
+ final Invoices invoicesForAccount = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+ final Invoice unpaidInvoice = invoicesForAccount.get(1);
+ assertEquals(unpaidInvoice.getBalance().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+ final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ assertEquals(paymentsForAccount.size(), 0);
+
+ final InvoicePayment invoicePaymentRequest = new InvoicePayment();
+ invoicePaymentRequest.setTargetInvoiceId(unpaidInvoice.getInvoiceId());
+ invoicePaymentRequest.setAccountId(accountJson.getAccountId());
+ invoicePaymentRequest.setCurrency(unpaidInvoice.getCurrency().toString());
+ invoicePaymentRequest.setPurchasedAmount(unpaidInvoice.getAmount());
+ final InvoicePayment invoicePayment = killBillClient.createInvoicePayment(invoicePaymentRequest, true, requestOptions);
+ assertEquals(invoicePayment.getPurchasedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ assertEquals(invoicePayment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+
+ final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+ invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+ invoicePaymentTransactionRequest.setPaymentId(invoicePayment.getPaymentId());
+ invoicePaymentTransactionRequest.setIsAdjusted(true);
+ invoicePaymentTransactionRequest.setAdjustments(unpaidInvoice.getItems());
+ final InvoicePayment invoicePaymentRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, requestOptions);
+ assertNotNull(invoicePaymentRefund);
+
+ assertSingleInvoicePaymentRefund(invoicePaymentRefund);
+ assertRefundInvoiceAdjustments(accountJson.getAccountId());
+ assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.ZERO, BigDecimal.ZERO);
+ }
+
+ @Test(groups = "slow", description = "#255 - Scenario 2a - Can refund an automatic payment though an external refund")
+ public void testAutomaticPaymentAndExternalRefund() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+ // delete PM
+ killBillClient.deletePaymentMethod(accountJson.getPaymentMethodId(), true, true, requestOptions);
+ final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+ // external refund
+ final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+ invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+ invoicePaymentTransactionRequest.setCurrency(accountJson.getCurrency().toString());
+ invoicePaymentTransactionRequest.setPaymentId(payment.getPaymentId());
+ final InvoicePayment invoicePaymentExternalRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, true, null, requestOptions);
+ assertNotNull(invoicePaymentExternalRefund);
+
+ assertInvoicePaymentsExternalRefund(accountJson.getAccountId(), invoicePaymentExternalRefund);
+ assertRefundInvoiceNoAdjustments(accountJson.getAccountId());
+ assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.valueOf(249.95), BigDecimal.ZERO);
+
+ }
+
+ @Test(groups = "slow", description = "#255 - Scenario 2a - Can refund an automatic payment though an external refund over item adjustments")
+ public void testAutomaticPaymentAndExternalRefundWithAdjustments() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+ // delete PM
+ killBillClient.deletePaymentMethod(accountJson.getPaymentMethodId(), true, true, requestOptions);
+
+ final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+ final Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, true, requestOptions);
+ final List<InvoiceItem> itemsToBeAdjusted = invoices.get(1).getItems();
+
+ // external refund
+ final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+ invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+ invoicePaymentTransactionRequest.setCurrency(accountJson.getCurrency().toString());
+ invoicePaymentTransactionRequest.setPaymentId(payment.getPaymentId());
+ invoicePaymentTransactionRequest.setIsAdjusted(true);
+ invoicePaymentTransactionRequest.setAdjustments(itemsToBeAdjusted);
+ final InvoicePayment invoicePaymentExternalRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, true, null, requestOptions);
+ assertNotNull(invoicePaymentExternalRefund);
+
+ assertInvoicePaymentsExternalRefund(accountJson.getAccountId(), invoicePaymentExternalRefund);
+ assertRefundInvoiceAdjustments(accountJson.getAccountId());
+ assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.ZERO, BigDecimal.ZERO);
+
+ }
+
+ @Test(groups = "slow", description = "#255 - Scenario 2b - Can refund an automatic payment though another existing payment method")
+ public void testAutomaticPaymentAndExternalRefundWithDifferentPM() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+ // delete PM
+ killBillClient.deletePaymentMethod(accountJson.getPaymentMethodId(), true, true, requestOptions);
+
+ // create another PM
+ final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
+ final PaymentMethod paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), false, PLUGIN_NAME, info);
+ final PaymentMethod otherPaymentMethod = killBillClient.createPaymentMethod(paymentMethodJson, requestOptions);
+
+ final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+ final Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, true, requestOptions);
+ final List<InvoiceItem> itemsToBeAdjusted = invoices.get(1).getItems();
+
+ // external refund
+ final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+ invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+ invoicePaymentTransactionRequest.setCurrency(accountJson.getCurrency().toString());
+ invoicePaymentTransactionRequest.setPaymentId(payment.getPaymentId());
+ invoicePaymentTransactionRequest.setIsAdjusted(true);
+ invoicePaymentTransactionRequest.setAdjustments(itemsToBeAdjusted);
+ final InvoicePayment invoicePaymentExternalRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, true, otherPaymentMethod.getPaymentMethodId(), requestOptions);
+ assertNotNull(invoicePaymentExternalRefund);
+ assertEquals(invoicePaymentExternalRefund.getPaymentMethodId(), otherPaymentMethod.getPaymentMethodId());
+
+ assertInvoicePaymentsExternalRefund(accountJson.getAccountId(), invoicePaymentExternalRefund);
+ assertRefundInvoiceAdjustments(accountJson.getAccountId());
+ assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.ZERO, BigDecimal.ZERO);
+
+ }
+
+ private void assertRefundInvoiceAdjustments(final UUID accountId) throws KillBillClientException {
+ final Invoices invoices;
+ invoices = killBillClient.getInvoicesForAccount(accountId, true, true, requestOptions);
+ final Invoice invoiceWithRefund = invoices.get(1);
+ assertEquals(invoiceWithRefund.getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(invoiceWithRefund.getRefundAdj().compareTo(BigDecimal.valueOf(249.95).negate()), 0);
+ assertEquals(invoiceWithRefund.getItems().size(), 2);
+ assertEquals(invoiceWithRefund.getItems().get(0).getItemType(), InvoiceItemType.RECURRING.toString());
+ assertEquals(invoiceWithRefund.getItems().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ assertEquals(invoiceWithRefund.getItems().get(1).getItemType(), InvoiceItemType.ITEM_ADJ.toString());
+ assertEquals(invoiceWithRefund.getItems().get(1).getAmount().compareTo(BigDecimal.valueOf(249.95).negate()), 0);
+ }
+
+ private void assertRefundInvoiceNoAdjustments(final UUID accountId) throws KillBillClientException {
+ final Invoices invoices = killBillClient.getInvoicesForAccount(accountId, true, true, requestOptions);
+ final Invoice invoiceWithRefund = invoices.get(1);
+ assertEquals(invoiceWithRefund.getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ assertEquals(invoiceWithRefund.getRefundAdj().compareTo(BigDecimal.valueOf(249.95).negate()), 0);
+ assertEquals(invoiceWithRefund.getItems().size(), 1);
+ assertEquals(invoiceWithRefund.getItems().get(0).getItemType(), InvoiceItemType.RECURRING.toString());
+ assertEquals(invoiceWithRefund.getItems().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ }
+
+ private void assertRefundAccountBalance(final UUID accountId, final BigDecimal balanceAmount, final BigDecimal cbaAmount) throws KillBillClientException {
+ final Account account = killBillClient.getAccount(accountId, true, true, requestOptions);
+ assertEquals(account.getAccountBalance().compareTo(balanceAmount), 0);
+ assertEquals(account.getAccountCBA().compareTo(cbaAmount), 0);
+ }
+
+ private void assertInvoicePaymentsExternalRefund(final UUID accountId, final InvoicePayment invoicePaymentExternalRefund) throws KillBillClientException {
+ final InvoicePayments invoicePaymentsForAccount = killBillClient.getInvoicePaymentsForAccount(accountId, requestOptions);
+ assertEquals(invoicePaymentsForAccount.size(), 2);
+
+ // INVOICE PAYMENT FOR ORIGINAL PURCHASE
+ final InvoicePayment invoicePaymentPurchase = invoicePaymentsForAccount.get(0);
+ assertEquals(invoicePaymentPurchase.getPurchasedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ assertEquals(invoicePaymentPurchase.getCreditedAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(invoicePaymentPurchase.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE.toString());
+ assertEquals(invoicePaymentPurchase.getTransactions().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+ // INVOICE PAYMENT FOR EXTERNAL REFUND
+ final InvoicePayment creditInvoicePayment = invoicePaymentsForAccount.get(1);
+ assertTrue(creditInvoicePayment.equals(invoicePaymentExternalRefund));
+
+ assertEquals(creditInvoicePayment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(creditInvoicePayment.getCreditedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ assertEquals(creditInvoicePayment.getTransactions().size(), 1);
+ assertEquals(creditInvoicePayment.getTransactions().get(0).getTransactionType(), TransactionType.CREDIT.toString());
+ assertEquals(creditInvoicePayment.getTransactions().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ }
+
+ private void assertSingleInvoicePaymentRefund(final InvoicePayment invoicePaymentRefund) {
+ // ONLY ONE INVOICE PAYMENT IS GENERATED FOR BOTH, PURCHASE AND REFUND.
+ assertEquals(invoicePaymentRefund.getPurchasedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ assertEquals(invoicePaymentRefund.getRefundedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+ assertEquals(invoicePaymentRefund.getTransactions().size(), 2);
+ assertEquals(invoicePaymentRefund.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE.toString());
+ assertEquals(invoicePaymentRefund.getTransactions().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ assertEquals(invoicePaymentRefund.getTransactions().get(1).getTransactionType(), TransactionType.REFUND.toString());
+ assertEquals(invoicePaymentRefund.getTransactions().get(1).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+ }
+
+}