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 e661f7a..2463abe 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
@@ -19,7 +19,6 @@ 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;
@@ -73,9 +72,7 @@ import org.killbill.commons.metrics.MetricTag;
import org.killbill.commons.metrics.TimedResource;
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;
@@ -266,6 +263,11 @@ public class PaymentResource extends ComboPaymentResource {
return completeTransactionInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
}
+
+
+
+
+
private Response completeTransactionInternal(final PaymentTransactionJson json,
@Nullable final String paymentIdStr,
final List<String> paymentControlPluginNames,
@@ -275,6 +277,7 @@ public class PaymentResource extends ComboPaymentResource {
final String comment,
final UriInfo uriInfo,
final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json == null ? null : json.getPaymentExternalKey(), pluginProperties, callContext);
@@ -283,96 +286,38 @@ public class PaymentResource extends ComboPaymentResource {
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.getTransactionId() != null) {
- final Collection<PaymentTransaction> paymentTransactionCandidates = Collections2.<PaymentTransaction>filter(initialPayment.getTransactions(),
- new Predicate<PaymentTransaction>() {
- @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) {
- final Collection<PaymentTransaction> paymentTransactionCandidates = Collections2.<PaymentTransaction>filter(initialPayment.getTransactions(),
- new Predicate<PaymentTransaction>() {
- @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<PaymentTransaction> paymentTransactionCandidates = Collections2.<PaymentTransaction>filter(initialPayment.getTransactions(),
- new Predicate<PaymentTransaction>() {
- @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 PaymentTransaction pendingTransaction = lookupPendingTransaction(initialPayment,
+ json != null ? json.getTransactionId() : null,
+ json != null ? json.getTransactionExternalKey() : null,
+ json != null ? json.getTransactionType() : null);
- final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
- switch (transactionType) {
+ final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+ switch (pendingTransaction.getTransactionType()) {
case AUTHORIZE:
paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
- initialPayment.getExternalKey(), transactionExternalKey,
+ initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case CAPTURE:
- paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, transactionExternalKey,
+ paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case PURCHASE:
paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
- initialPayment.getExternalKey(), transactionExternalKey,
+ initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case CREDIT:
paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
- initialPayment.getExternalKey(), transactionExternalKey,
+ initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case REFUND:
paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
- transactionExternalKey, pluginProperties, paymentOptions, callContext);
+ pendingTransaction.getExternalKey(), pluginProperties, paymentOptions, callContext);
break;
default:
- return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " cannot be completed").build();
+ return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " 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 b76434b..b46915f 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,7 @@ 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.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
@@ -47,6 +48,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
public class TestPayment extends TestJaxrsBase {
@@ -139,6 +141,185 @@ public class TestPayment extends TestJaxrsBase {
}
@Test(groups = "slow")
+ public void testAuthorizeCompletionUsingPaymentId() throws Exception {
+ final Account account = createAccountWithDefaultPaymentMethod();
+ final UUID paymentMethodId = account.getPaymentMethodId();
+ final BigDecimal amount = BigDecimal.TEN;
+
+ final String pending = PaymentPluginStatus.PENDING.toString();
+ final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+ final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+ TransactionType transactionType = TransactionType.AUTHORIZE;
+ final String paymentExternalKey = UUID.randomUUID().toString();
+ final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+ final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+ // 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(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+ }
+
+
+ @Test(groups = "slow")
+ public void testAuthorizeCompletionUsingPaymentIdAndTransactionId() throws Exception {
+ final Account account = createAccountWithDefaultPaymentMethod();
+ final UUID paymentMethodId = account.getPaymentMethodId();
+ final BigDecimal amount = BigDecimal.TEN;
+
+ final String pending = PaymentPluginStatus.PENDING.toString();
+ final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+ final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+ TransactionType transactionType = TransactionType.AUTHORIZE;
+ final String paymentExternalKey = UUID.randomUUID().toString();
+ final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+ final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+
+ final PaymentTransaction completeTransactionByPaymentIdAndInvalidTransactionId = new PaymentTransaction();
+ completeTransactionByPaymentIdAndInvalidTransactionId.setPaymentId(initialPayment.getPaymentId());
+ completeTransactionByPaymentIdAndInvalidTransactionId.setTransactionId(UUID.randomUUID());
+ try {
+ killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionId, pluginProperties, createdBy, reason, comment);
+ fail("Payment completion should fail when invalid transaction id has been provided" );
+ } catch (final KillBillClientException expected) {
+ }
+
+ final PaymentTransaction completeTransactionByPaymentIdAndTransactionId = new PaymentTransaction();
+ completeTransactionByPaymentIdAndTransactionId.setPaymentId(initialPayment.getPaymentId());
+ completeTransactionByPaymentIdAndTransactionId.setTransactionId(initialPayment.getTransactions().get(0).getTransactionId());
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionId, pluginProperties, createdBy, reason, comment);
+ verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+ }
+
+ @Test(groups = "slow")
+ public void testAuthorizeCompletionUsingPaymentIdAndTransactionExternalKey() throws Exception {
+ final Account account = createAccountWithDefaultPaymentMethod();
+ final UUID paymentMethodId = account.getPaymentMethodId();
+ final BigDecimal amount = BigDecimal.TEN;
+
+ final String pending = PaymentPluginStatus.PENDING.toString();
+ final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+ final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+ TransactionType transactionType = TransactionType.AUTHORIZE;
+ final String paymentExternalKey = UUID.randomUUID().toString();
+ final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+ final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+ final PaymentTransaction completeTransactionByPaymentIdAndInvalidTransactionExternalKey = new PaymentTransaction();
+ completeTransactionByPaymentIdAndInvalidTransactionExternalKey.setPaymentId(initialPayment.getPaymentId());
+ completeTransactionByPaymentIdAndInvalidTransactionExternalKey.setTransactionExternalKey("bozo");
+ try {
+ killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionExternalKey, pluginProperties, createdBy, reason, comment);
+ fail("Payment completion should fail when invalid transaction externalKey has been provided" );
+ } catch (final KillBillClientException expected) {
+ }
+
+ final PaymentTransaction completeTransactionByPaymentIdAndTransactionExternalKey = new PaymentTransaction();
+ completeTransactionByPaymentIdAndTransactionExternalKey.setPaymentId(initialPayment.getPaymentId());
+ completeTransactionByPaymentIdAndTransactionExternalKey.setTransactionExternalKey(authTransactionExternalKey);
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionExternalKey, pluginProperties, createdBy, reason, comment);
+ verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+ }
+
+
+ @Test(groups = "slow")
+ public void testAuthorizeCompletionUsingPaymentIdAndTransactionType() throws Exception {
+ final Account account = createAccountWithDefaultPaymentMethod();
+ final UUID paymentMethodId = account.getPaymentMethodId();
+ final BigDecimal amount = BigDecimal.TEN;
+
+ final String pending = PaymentPluginStatus.PENDING.toString();
+ final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+ final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+ TransactionType transactionType = TransactionType.AUTHORIZE;
+ final String paymentExternalKey = UUID.randomUUID().toString();
+ final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+ final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+
+ final PaymentTransaction completeTransactionByPaymentIdAndInvalidTransactionType = new PaymentTransaction();
+ completeTransactionByPaymentIdAndInvalidTransactionType.setPaymentId(initialPayment.getPaymentId());
+ completeTransactionByPaymentIdAndInvalidTransactionType.setTransactionType(TransactionType.CAPTURE.name());
+ try {
+ killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionType, pluginProperties, createdBy, reason, comment);
+ fail("Payment completion should fail when invalid transaction type has been provided" );
+ } catch (final KillBillClientException expected) {
+ }
+
+ final PaymentTransaction completeTransactionByPaymentIdAndTransactionType = new PaymentTransaction();
+ completeTransactionByPaymentIdAndTransactionType.setPaymentId(initialPayment.getPaymentId());
+ completeTransactionByPaymentIdAndTransactionType.setTransactionType(transactionType.name());
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionType, pluginProperties, createdBy, reason, comment);
+ verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+ }
+
+ @Test(groups = "slow")
+ public void testAuthorizeCompletionUsingExternalKey() throws Exception {
+
+ final Account account = createAccountWithDefaultPaymentMethod();
+ final UUID paymentMethodId = account.getPaymentMethodId();
+ final BigDecimal amount = BigDecimal.TEN;
+
+ final String pending = PaymentPluginStatus.PENDING.toString();
+ final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+ final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+ TransactionType transactionType = TransactionType.AUTHORIZE;
+ final String paymentExternalKey = UUID.randomUUID().toString();
+ final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+ final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+ final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction();
+ completeTransactionWithTypeAndKey.setPaymentId(initialPayment.getPaymentId());
+ completeTransactionWithTypeAndKey.setTransactionExternalKey(authTransactionExternalKey);
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, createdBy, reason, comment);
+ verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+ }
+
+
+ @Test(groups = "slow")
+ public void testAuthorizeInvalidCompletionUsingPaymentId() throws Exception {
+ final Account account = createAccountWithDefaultPaymentMethod();
+ final UUID paymentMethodId = account.getPaymentMethodId();
+ final BigDecimal amount = BigDecimal.TEN;
+
+ final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+ TransactionType transactionType = TransactionType.AUTHORIZE;
+ final String paymentExternalKey = UUID.randomUUID().toString();
+ final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+ final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, TransactionStatus.SUCCESS.name(), amount, amount, pluginProperties, 1);
+
+ // The payment was already completed
+ final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
+ completeTransactionByPaymentId.setPaymentId(initialPayment.getPaymentId());
+ try {
+ killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment);
+ fail("Completion should not succeed, there is no PENDING payment transaction");
+ } catch (final KillBillClientException expected) {
+ // Invalid parameter paymentId: XXXX
+ }
+ }
+
+
+ @Test(groups = "slow")
public void testCompletionForSubsequentTransaction() throws Exception {
final Account account = createAccountWithDefaultPaymentMethod();
final UUID paymentMethodId = account.getPaymentMethodId();
@@ -163,27 +344,7 @@ public class TestPayment extends TestJaxrsBase {
final Payment refundPayment = killBillClient.refundPayment(refundTransaction, null, 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) {
- 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) {
- 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);