killbill-memoizeit
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java 8(+6 -2)
invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java 13(+9 -4)
payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java 31(+29 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java 4(+2 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java 52(+52 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java 33(+24 -9)
payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java 10(+8 -2)
Details
diff --git a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
index 5f9d7b5..a832284 100644
--- a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2011 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -62,7 +62,9 @@ public interface InvoiceInternalApi {
public InvoicePayment recordRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
String transactionExternalKey, InternalCallContext context) throws InvoiceApiException;
- public InvoicePayment recordChargeback(UUID paymentId, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
+ public InvoicePayment recordChargeback(UUID paymentId, String chargebackTransactionExternalKey, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
+
+ public InvoicePayment recordChargebackReversal(UUID paymentId, String chargebackTransactionExternalKey, InternalCallContext context) throws InvoiceApiException;
/**
* Rebalance CBA for account which have credit and unpaid invoices
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index 8beca38..2fd7a3d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -804,11 +804,15 @@ public class TestOverdueIntegration extends TestOverdueBase {
// Now, create a chargeback for the second (first non-zero dollar) invoice
final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayments(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).get(1).getPayments().get(0).getPaymentId(), callContext).get(0);
- final Payment payment = paymentApi.getPayment(invoicePayment.getPaymentId(), false, ImmutableList.<PluginProperty>of(), callContext);
- createChargeBackAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
+ Payment payment = paymentApi.getPayment(invoicePayment.getPaymentId(), false, ImmutableList.<PluginProperty>of(), callContext);
+ payment = createChargeBackAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
// We should now be in OD1
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
+
+ // Reverse the chargeback
+ createChargeBackReversalAndCheckForCompletion(account, payment, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.BLOCK);
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
}
@Test(groups = "slow", description = "Test overdue clear after external payment")
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index a9605bd..bd25206 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -84,8 +84,11 @@ import org.killbill.billing.payment.api.PaymentApi;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PaymentMethodPlugin;
import org.killbill.billing.payment.api.PaymentOptions;
+import org.killbill.billing.payment.api.PaymentTransaction;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TestPaymentMethodPluginBase;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
import org.killbill.billing.subscription.api.SubscriptionBase;
@@ -105,7 +108,6 @@ import org.killbill.billing.util.nodes.KillbillNodesApi;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
import org.killbill.bus.api.PersistentBus;
-import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterMethod;
@@ -114,7 +116,10 @@ import org.testng.annotations.BeforeMethod;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
@@ -540,6 +545,32 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
}, events);
}
+ protected Payment createChargeBackReversalAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
+ final PaymentTransaction chargeback = Iterables.<PaymentTransaction>find(Lists.<PaymentTransaction>reverse(payment.getTransactions()),
+ new Predicate<PaymentTransaction>() {
+ @Override
+ public boolean apply(final PaymentTransaction input) {
+ return TransactionType.CHARGEBACK.equals(input.getTransactionType()) &&
+ TransactionStatus.SUCCESS.equals(input.getTransactionStatus());
+ }
+ });
+ return createChargeBackReversalAndCheckForCompletion(account, payment, chargeback.getExternalKey(), events);
+ }
+
+ protected Payment createChargeBackReversalAndCheckForCompletion(final Account account, final Payment payment, final String chargebackTransactionExternalKey, final NextEvent... events) {
+ return doCallAndCheckForCompletion(new Function<Void, Payment>() {
+ @Override
+ public Payment apply(@Nullable final Void input) {
+ try {
+ return paymentApi.createChargebackReversalWithPaymentControl(account, payment.getId(), chargebackTransactionExternalKey, PAYMENT_OPTIONS, callContext);
+ } catch (final PaymentApiException e) {
+ fail(e.toString());
+ return null;
+ }
+ }
+ }, events);
+ }
+
protected DefaultEntitlement createBaseEntitlementWithPriceOverrideAndCheckForCompletion(final UUID accountId,
final String bundleExternalKey,
final String productName,
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
index 836d7d6..7c0a317 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
@@ -195,7 +195,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
}
@Test(groups = "slow")
- public void testPartialPaymentByPaymentPluginThenChargeback() throws Exception {
+ public void testPartialPaymentByPaymentPluginThenChargebackThenChargebackReversal() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -235,6 +235,20 @@ public class TestInvoicePayment extends TestIntegrationBase {
invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("249.95")), 0);
Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+ // Trigger chargeback reversal
+ payment1 = createChargeBackReversalAndCheckForCompletion(account, payment1, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
+ Assert.assertEquals(payment1.getTransactions().size(), 3);
+ Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+ Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+ Assert.assertEquals(payment1.getTransactions().get(1).getAmount().compareTo(BigDecimal.TEN), 0);
+ Assert.assertEquals(payment1.getTransactions().get(1).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+ Assert.assertNull(payment1.getTransactions().get(2).getAmount());
+ Assert.assertEquals(payment1.getTransactions().get(2).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
+ invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+ Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("239.95")), 0);
+ Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
}
@Test(groups = "slow")
@@ -334,6 +348,26 @@ public class TestInvoicePayment extends TestIntegrationBase {
invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("249.95")), 0);
Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+ // Reverse the chargeback
+ payment1 = createChargeBackReversalAndCheckForCompletion(account, payment1, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ Assert.assertEquals(payment1.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+ Assert.assertEquals(payment1.getTransactions().size(), 3);
+ Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+ Assert.assertEquals(payment1.getTransactions().get(0).getCurrency(), Currency.USD);
+ Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("225.44")), 0);
+ Assert.assertEquals(payment1.getTransactions().get(0).getProcessedCurrency(), Currency.EUR);
+ Assert.assertEquals(payment1.getTransactions().get(1).getAmount().compareTo(new BigDecimal("225.44")), 0);
+ Assert.assertEquals(payment1.getTransactions().get(1).getCurrency(), Currency.EUR);
+ Assert.assertEquals(payment1.getTransactions().get(1).getProcessedAmount().compareTo(new BigDecimal("225.44")), 0);
+ Assert.assertEquals(payment1.getTransactions().get(1).getProcessedCurrency(), Currency.EUR);
+ Assert.assertNull(payment1.getTransactions().get(2).getAmount());
+ Assert.assertNull(payment1.getTransactions().get(2).getCurrency());
+ Assert.assertEquals(payment1.getTransactions().get(2).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
+ Assert.assertNull(payment1.getTransactions().get(2).getProcessedCurrency());
+ invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+ Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
+ Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
}
@Test(groups = "slow")
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index b0bfcf1..50c9f0e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -146,8 +146,13 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
}
@Override
- public InvoicePayment recordChargeback(final UUID paymentId, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
- return new DefaultInvoicePayment(dao.postChargeback(paymentId, amount, currency, context));
+ public InvoicePayment recordChargeback(final UUID paymentId, final String chargebackTransactionExternalKey, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
+ return new DefaultInvoicePayment(dao.postChargeback(paymentId, chargebackTransactionExternalKey, amount, currency, context));
+ }
+
+ @Override
+ public InvoicePayment recordChargebackReversal(final UUID paymentId, final String chargebackTransactionExternalKey, final InternalCallContext context) throws InvoiceApiException {
+ return new DefaultInvoicePayment(dao.postChargebackReversal(paymentId, chargebackTransactionExternalKey, context));
}
@Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index ca236f4..aba0d98 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -469,7 +469,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// Before we go further, check if that refund already got inserted -- the payment system keeps a state machine
// and so this call may be called several time for the same paymentCookieId (which is really the refundId)
- final InvoicePaymentModelDao existingRefund = transactional.getPaymentsForCookieId(transactionExternalKey, context);
+ final InvoicePaymentModelDao existingRefund = transactional.getPaymentForCookieId(transactionExternalKey, context);
if (existingRefund != null) {
return existingRefund;
}
@@ -517,7 +517,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
@Override
- public InvoicePaymentModelDao postChargeback(final UUID paymentId, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
+ public InvoicePaymentModelDao postChargeback(final UUID paymentId, final String chargebackTransactionExternalKey, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<InvoicePaymentModelDao>() {
@Override
public InvoicePaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -554,7 +554,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
final InvoicePaymentModelDao chargeBack = new InvoicePaymentModelDao(UUIDs.randomUUID(), context.getCreatedDate(), InvoicePaymentType.CHARGED_BACK,
payment.getInvoiceId(), payment.getPaymentId(), context.getCreatedDate(),
requestedChargedBackAmount.negate(), payment.getCurrency(), payment.getProcessedCurrency(),
- null, payment.getId(), true);
+ chargebackTransactionExternalKey, payment.getId(), true);
transactional.create(chargeBack, context);
// Notify the bus since the balance of the invoice changed
@@ -570,6 +570,42 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
@Override
+ public InvoicePaymentModelDao postChargebackReversal(final UUID paymentId, final String chargebackTransactionExternalKey, final InternalCallContext context) throws InvoiceApiException {
+ return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<InvoicePaymentModelDao>() {
+ @Override
+ public InvoicePaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
+
+ final InvoicePaymentModelDao invoicePayment = transactional.getPaymentForCookieId(chargebackTransactionExternalKey, context);
+ if (invoicePayment == null) {
+ throw new InvoiceApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
+ }
+
+ transactional.updateAttempt(invoicePayment.getRecordId(),
+ invoicePayment.getPaymentId().toString(),
+ invoicePayment.getPaymentDate().toDate(),
+ invoicePayment.getAmount(),
+ invoicePayment.getCurrency(),
+ invoicePayment.getProcessedCurrency(),
+ invoicePayment.getPaymentCookieId(),
+ invoicePayment.getLinkedInvoicePaymentId() == null ? null : invoicePayment.getLinkedInvoicePaymentId().toString(),
+ false,
+ context);
+ final InvoicePaymentModelDao chargebackReversed = transactional.getByRecordId(invoicePayment.getRecordId(), context);
+
+ // Notify the bus since the balance of the invoice changed
+ final UUID accountId = transactional.getAccountIdFromInvoicePaymentId(chargebackReversed.getId().toString(), context);
+
+ cbaDao.addCBAComplexityFromTransaction(chargebackReversed.getInvoiceId(), entitySqlDaoWrapperFactory, context);
+
+ notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, chargebackReversed, accountId, context.getUserToken(), context);
+
+ return chargebackReversed;
+ }
+ });
+ }
+
+ @Override
public InvoiceItemModelDao doCBAComplexity(final InvoiceModelDao invoice, final InternalCallContext context) throws InvoiceApiException {
return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<InvoiceItemModelDao>() {
@Override
@@ -712,6 +748,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
invoicePayment.getProcessedCurrency(),
invoicePayment.getPaymentCookieId(),
null,
+ true,
context);
}
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
index 3ee47be..fbcc451 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -32,7 +32,6 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
-import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.EntityDao;
@@ -72,7 +71,9 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
// Include migrated invoices
List<InvoiceModelDao> getAllInvoicesByAccount(InternalTenantContext context);
- InvoicePaymentModelDao postChargeback(UUID paymentId, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
+ InvoicePaymentModelDao postChargeback(UUID paymentId, String chargebackTransactionExternalKey, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
+
+ InvoicePaymentModelDao postChargebackReversal(UUID paymentId, String chargebackTransactionExternalKey, InternalCallContext context) throws InvoiceApiException;
InvoiceItemModelDao doCBAComplexity(InvoiceModelDao invoice, InternalCallContext context) throws InvoiceApiException;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
index 7b8c5ad..658dda0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -49,8 +49,8 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDa
@BindBean final InternalTenantContext context);
@SqlQuery
- InvoicePaymentModelDao getPaymentsForCookieId(@Bind("paymentCookieId") final String paymentCookieId,
- @BindBean final InternalTenantContext context);
+ InvoicePaymentModelDao getPaymentForCookieId(@Bind("paymentCookieId") final String paymentCookieId,
+ @BindBean final InternalTenantContext context);
@SqlQuery
BigDecimal getRemainingAmountPaid(@Bind("invoicePaymentId") final String invoicePaymentId,
@@ -68,7 +68,6 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDa
List<InvoicePaymentModelDao> getChargebacksByPaymentId(@Bind("paymentId") final String paymentId,
@BindBean final InternalTenantContext context);
-
@SqlUpdate
void updateAttempt(@Bind("recordId") Long recordId,
@Bind("paymentId") final String paymentId,
@@ -78,5 +77,6 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDa
@Bind("processedCurrency") final Currency processedCurrency,
@Bind("paymentCookieId") final String paymentCookieId,
@Bind("linkedInvoicePaymentId") final String linkedInvoicePaymentId,
+ @Bind("success") final boolean success,
@BindBean final InternalTenantContext context);
}
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index 9925595..13c569b 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -41,12 +41,13 @@ getByPaymentId() ::= <<
;
>>
-getPaymentsForCookieId() ::= <<
+getPaymentForCookieId() ::= <<
SELECT <allTableFields()>
FROM <tableName()>
WHERE payment_cookie_id = :paymentCookieId
<AND_CHECK_TENANT()>
<defaultOrderBy()>
+ LIMIT 1
;
>>
@@ -64,7 +65,7 @@ getInvoicePayments() ::= <<
FROM <tableName()>
WHERE payment_id = :paymentId
<AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ <defaultOrderBy()>
;
>>
@@ -92,6 +93,7 @@ getChargeBacksByAccountId() ::= <<
FROM <tableName()> ip
INNER JOIN invoices i ON i.id = ip.invoice_id
WHERE ip.type = 'CHARGED_BACK' AND i.account_id = :accountId
+ AND success
<AND_CHECK_TENANT("i.")>
<AND_CHECK_TENANT("ip.")>
;
@@ -102,11 +104,11 @@ getChargebacksByPaymentId() ::= <<
FROM <tableName()>
WHERE type = 'CHARGED_BACK'
AND linked_invoice_payment_id IN (SELECT id FROM invoice_payments WHERE payment_id = :paymentId)
+ AND success
<AND_CHECK_TENANT()>
;
>>
-
updateAttempt() ::= <<
UPDATE <tableName()>
SET payment_id := :paymentId,
@@ -116,7 +118,7 @@ updateAttempt() ::= <<
processed_currency = :processedCurrency,
payment_cookie_id = :paymentCookieId,
linked_invoice_payment_id := :linkedInvoicePaymentId,
- success = true
+ success := :success
WHERE record_id = :recordId
<AND_CHECK_TENANT("")>
;
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
index 2164386..aded6e7 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -294,7 +294,12 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
}
@Override
- public InvoicePaymentModelDao postChargeback(final UUID invoicePaymentId, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
+ public InvoicePaymentModelDao postChargeback(final UUID invoicePaymentId, final String chargebackTransactionExternalKey, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InvoicePaymentModelDao postChargebackReversal(final UUID paymentId, final String chargebackTransactionExternalKey, final InternalCallContext context) throws InvoiceApiException {
throw new UnsupportedOperationException();
}
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 4b36164..74e1588 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * 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
@@ -211,6 +211,33 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
}
@TimedResource
+ @POST
+ @Path("/{paymentId:" + UUID_PATTERN + "}/" + CHARGEBACK_REVERSALS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Record a chargebackReversal")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid payment id supplied"),
+ @ApiResponse(code = 404, message = "Account or payment not found")})
+ public Response createChargebackReversal(final InvoicePaymentTransactionJson json,
+ @PathParam("paymentId") final String paymentId,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final UriInfo uriInfo,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+ verifyNonNullOrEmpty(json, "InvoicePaymentTransactionJson body should be specified");
+ verifyNonNullOrEmpty(json.getTransactionExternalKey(), "transactionExternalKey amount needs to be set");
+
+ final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+ final UUID paymentUuid = UUID.fromString(paymentId);
+ final Payment payment = paymentApi.getPayment(paymentUuid, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
+
+ final Payment result = paymentApi.createChargebackReversalWithPaymentControl(account, payment.getId(), json.getTransactionExternalKey(), createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+ return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId());
+ }
+
+ @TimedResource
@GET
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index e07b770..1ca26ae 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * 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
@@ -189,6 +189,9 @@ public interface JaxrsResource {
public static final String CHARGEBACKS = "chargebacks";
public static final String CHARGEBACKS_PATH = PREFIX + "/" + CHARGEBACKS;
+ public static final String CHARGEBACK_REVERSALS = "chargebackReversals";
+ public static final String CHARGEBACK_REVERSALS_PATH = PREFIX + "/" + CHARGEBACK_REVERSALS;
+
public static final String ALL_TAGS = "allTags";
public static final String TAGS = "tags";
public static final String TAGS_PATH = PREFIX + "/" + TAGS;
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 5a424b3..3c2b1bb 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -638,6 +638,80 @@ public class PaymentResource extends ComboPaymentResource {
return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey());
}
+ @TimedResource(name = "chargebackReversalPayment")
+ @POST
+ @Path("/{paymentId:" + UUID_PATTERN + "}/" + CHARGEBACK_REVERSALS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Record a chargeback reversal")
+ @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+ @ApiResponse(code = 400, message = "Invalid paymentId supplied"),
+ @ApiResponse(code = 404, message = "Account or payment not found"),
+ @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+ @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+ @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+ @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+ @ApiResponse(code = 504, message = "Payment operation timeout")})
+ public Response chargebackReversalPayment(final PaymentTransactionJson json,
+ @PathParam("paymentId") final String paymentIdStr,
+ @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
+ @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final UriInfo uriInfo,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+ return chargebackReversalPaymentInternal(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+ }
+
+ @TimedResource(name = "chargebackReversalPayment")
+ @POST
+ @Path("/" + CHARGEBACK_REVERSALS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Record a chargeback reversal")
+ @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+ @ApiResponse(code = 404, message = "Account or payment not found"),
+ @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+ @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+ @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+ @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+ @ApiResponse(code = 504, message = "Payment operation timeout")})
+ public Response chargebackReversalPaymentByExternalKey(final PaymentTransactionJson json,
+ @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
+ @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final UriInfo uriInfo,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+ return chargebackReversalPaymentInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+ }
+
+ private Response chargebackReversalPaymentInternal(final PaymentTransactionJson json,
+ @Nullable final String paymentIdStr,
+ final List<String> paymentControlPluginNames,
+ final List<String> pluginPropertiesString,
+ final String createdBy,
+ final String reason,
+ final String comment,
+ final UriInfo uriInfo,
+ final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+ verifyNonNullOrEmpty(json, "PaymentTransactionJson body should be specified");
+ verifyNonNullOrEmpty(json.getTransactionExternalKey(), "PaymentTransactionJson transactionExternalKey needs to be set");
+
+ final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+ final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json.getPaymentExternalKey(), pluginProperties, callContext);
+
+ final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
+
+ final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+
+ final Payment payment = paymentApi.createChargebackReversalWithPaymentControl(account, initialPayment.getId(), json.getTransactionExternalKey(), paymentOptions, callContext);
+ return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey());
+ }
+
@TimedResource
@POST
@Consumes(APPLICATION_JSON)
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 d176726..e0751bd 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
@@ -662,7 +662,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
}
- //@Override TODO 0.17
+ @Override
public Payment createChargebackReversal(final Account account, final UUID paymentId, final String paymentTransactionExternalKey, final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
checkNotNullParameter(paymentId, "paymentId");
@@ -703,6 +703,51 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
+ public Payment createChargebackReversalWithPaymentControl(final Account account, final UUID paymentId, final String paymentTransactionExternalKey, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+ final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
+ if (paymentControlPluginNames.isEmpty()) {
+ return createChargebackReversal(account, paymentId, paymentTransactionExternalKey, callContext);
+ }
+
+ checkNotNullParameter(account, "account");
+ checkNotNullParameter(paymentId, "paymentId");
+ checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey");
+
+ final String transactionType = TransactionType.CHARGEBACK.name();
+ Payment payment = null;
+ PaymentTransaction paymentTransaction = null;
+ try {
+ logEnterAPICall(transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
+
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+ payment = pluginControlPaymentProcessor.createChargebackReversal(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey, paymentControlPluginNames, callContext, internalCallContext);
+
+ // See https://github.com/killbill/killbill/issues/552
+ paymentTransaction = Iterables.<PaymentTransaction>find(Lists.<PaymentTransaction>reverse(payment.getTransactions()),
+ new Predicate<PaymentTransaction>() {
+ @Override
+ public boolean apply(final PaymentTransaction input) {
+ return paymentTransactionExternalKey.equals(input.getExternalKey());
+ }
+ });
+
+ return payment;
+ } finally {
+ logExitAPICall(transactionType,
+ account,
+ payment != null ? payment.getPaymentMethodId() : null,
+ payment != null ? payment.getId() : null,
+ paymentTransaction != null ? paymentTransaction.getId() : null,
+ paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+ paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+ payment != null ? payment.getExternalKey() : null,
+ paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+ paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+ paymentControlPluginNames);
+ }
+ }
+
+ @Override
public List<Payment> getAccountPayments(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentApiException {
return paymentProcessor.getAccountPayments(accountId, withPluginInfo, tenantContext, internalCallContextFactory.createInternalTenantContext(accountId, tenantContext));
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
index 17e87d8..9ace343 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -40,6 +40,7 @@ import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper;
import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner.ControlOperation;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentModelDao;
@@ -86,6 +87,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
paymentId,
@@ -104,6 +106,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.CAPTURE,
+ ControlOperation.CAPTURE,
account,
null,
paymentId,
@@ -121,6 +124,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.PURCHASE,
+ ControlOperation.PURCHASE,
account,
paymentMethodId,
paymentId,
@@ -137,6 +141,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.VOID,
+ ControlOperation.VOID,
account,
null,
paymentId,
@@ -153,6 +158,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.REFUND,
+ ControlOperation.REFUND,
account,
null,
paymentId,
@@ -170,6 +176,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.CREDIT,
+ ControlOperation.CREDIT,
account,
paymentMethodId,
paymentId,
@@ -186,6 +193,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.CHARGEBACK,
+ ControlOperation.CHARGEBACK,
account,
null,
paymentId,
@@ -198,6 +206,24 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
callContext, internalCallContext);
}
+ public Payment createChargebackReversal(final boolean isApiPayment, final Account account, final UUID paymentId, final String transactionExternalKey,
+ final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+ return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
+ TransactionType.CHARGEBACK,
+ ControlOperation.CHARGEBACK_REVERSAL,
+ account,
+ null,
+ paymentId,
+ null,
+ transactionExternalKey,
+ null,
+ null,
+ ImmutableList.<PluginProperty>of(),
+ paymentControlPluginNames,
+ callContext,
+ internalCallContext);
+ }
+
public void retryPaymentTransaction(final UUID attemptId, final List<String> paymentControlPluginNames, final InternalCallContext internalCallContext) {
try {
final PaymentAttemptModelDao attempt = paymentDao.getPaymentAttempt(attemptId, internalCallContext);
@@ -216,6 +242,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
pluginControlledPaymentAutomatonRunner.run(state,
false,
attempt.getTransactionType(),
+ ControlOperation.valueOf(attempt.getTransactionType().toString()),
account,
attempt.getPaymentMethodId(),
paymentId,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
index 6837d0f..99e6a97 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * 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
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java
new file mode 100644
index 0000000..a0af618
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 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.payment.core.sm.control;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.util.config.definition.PaymentConfig;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class ChargebackReversalControlOperation extends OperationControlCallback {
+
+ public ChargebackReversalControlOperation(final GlobalLocker locker,
+ final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+ final PaymentConfig paymentConfig,
+ final PaymentStateControlContext paymentStateContext,
+ final PaymentProcessor paymentProcessor,
+ final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
+ }
+
+ @Override
+ protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+ return paymentProcessor.createChargebackReversal(paymentStateControlContext.isApiPayment(),
+ paymentStateControlContext.getAttemptId(),
+ paymentStateControlContext.getAccount(),
+ paymentStateControlContext.getPaymentId(),
+ paymentStateControlContext.getPaymentTransactionExternalKey(),
+ paymentStateControlContext.getAmount(),
+ paymentStateControlContext.getCurrency(),
+ false,
+ paymentStateControlContext.getCallContext(),
+ paymentStateControlContext.getInternalCallContext());
+ }
+}
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 f8e27a7..975c4b8 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
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -47,6 +47,7 @@ import org.killbill.billing.payment.core.PaymentProcessor;
import org.killbill.billing.payment.core.sm.control.AuthorizeControlOperation;
import org.killbill.billing.payment.core.sm.control.CaptureControlOperation;
import org.killbill.billing.payment.core.sm.control.ChargebackControlOperation;
+import org.killbill.billing.payment.core.sm.control.ChargebackReversalControlOperation;
import org.killbill.billing.payment.core.sm.control.CompletionControlOperation;
import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
import org.killbill.billing.payment.core.sm.control.CreditControlOperation;
@@ -74,6 +75,17 @@ import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner {
+ public enum ControlOperation {
+ AUTHORIZE,
+ CAPTURE,
+ CHARGEBACK,
+ CHARGEBACK_REVERSAL,
+ CREDIT,
+ PURCHASE,
+ REFUND,
+ VOID
+ }
+
protected final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry;
private final PaymentProcessor paymentProcessor;
private final RetryServiceScheduler retryServiceScheduler;
@@ -95,16 +107,16 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
this.paymentConfig = paymentConfig;
}
- public Payment run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
+ public Payment run(final boolean isApiPayment, final TransactionType transactionType, final ControlOperation controlOperation, final Account account, @Nullable final UUID paymentMethodId,
@Nullable final UUID paymentId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
@Nullable final BigDecimal amount, @Nullable final Currency currency,
final Iterable<PluginProperty> properties, @Nullable final List<String> paymentControlPluginNames,
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return run(paymentControlStateMachineHelper.getInitialState(), isApiPayment, transactionType, account, paymentMethodId, paymentId, paymentExternalKey, paymentTransactionExternalKey,
+ return run(paymentControlStateMachineHelper.getInitialState(), isApiPayment, transactionType, controlOperation, account, paymentMethodId, paymentId, paymentExternalKey, paymentTransactionExternalKey,
amount, currency, properties, paymentControlPluginNames, callContext, internalCallContext);
}
- public Payment run(final State state, final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
+ public Payment run(final State state, final boolean isApiPayment, final TransactionType transactionType, final ControlOperation controlOperation, final Account account, @Nullable final UUID paymentMethodId,
@Nullable final UUID paymentId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
@Nullable final BigDecimal amount, @Nullable final Currency currency,
final Iterable<PluginProperty> properties, @Nullable final List<String> paymentControlPluginNames,
@@ -115,7 +127,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
amount, currency,
properties, paymentControlPluginNames, callContext, internalCallContext);
try {
- final OperationCallback callback = createOperationCallback(transactionType, paymentStateContext);
+ final OperationCallback callback = createOperationCallback(controlOperation, paymentStateContext);
final LeavingStateCallback leavingStateCallback = new DefaultControlInitiated(this, paymentStateContext, paymentDao, paymentControlStateMachineHelper.getInitialState(), paymentControlStateMachineHelper.getRetriedState(), transactionType);
final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler);
@@ -168,9 +180,9 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
}
@VisibleForTesting
- OperationCallback createOperationCallback(final TransactionType transactionType, final PaymentStateControlContext paymentStateContext) {
+ OperationCallback createOperationCallback(final ControlOperation controlOperation, final PaymentStateControlContext paymentStateContext) {
final OperationCallback callback;
- switch (transactionType) {
+ switch (controlOperation) {
case AUTHORIZE:
callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
@@ -192,8 +204,11 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
case CHARGEBACK:
callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
+ case CHARGEBACK_REVERSAL:
+ callback = new ChargebackReversalControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
+ break;
default:
- throw new IllegalStateException("Unsupported transaction type " + transactionType);
+ throw new IllegalStateException("Unsupported control operation " + controlOperation);
}
return callback;
}
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 ea60cdf..eb2e21c 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
@@ -205,7 +205,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
currency = linkedInvoicePayment.getCurrency();
}
- invoiceApi.recordChargeback(paymentControlContext.getPaymentId(), amount, currency, internalContext);
+ invoiceApi.recordChargeback(paymentControlContext.getPaymentId(), paymentControlContext.getTransactionExternalKey(), amount, currency, internalContext);
}
break;
@@ -247,8 +247,14 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
break;
case REFUND:
+ // We don't retry REFUND
+ break;
case CHARGEBACK:
- // We don't retry REFUND, CHARGEBACK
+ try {
+ invoiceApi.recordChargebackReversal(paymentControlContext.getPaymentId(), paymentControlContext.getTransactionExternalKey(), internalContext);
+ } catch (final InvoiceApiException e) {
+ log.warn("onFailureCall failed for attemptId='{}', transactionType='{}'", paymentControlContext.getAttemptPaymentId(), transactionType, e);
+ }
break;
default:
throw new IllegalStateException("Unexpected transactionType " + transactionType);
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index a00a2c2..78437bc 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -1048,9 +1048,6 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
@Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/477")
public void testCreateChargeback() throws PaymentApiException {
- // API change in 0.17
- final DefaultPaymentApi paymentApi = (DefaultPaymentApi) this.paymentApi;
-
final BigDecimal requestedAmount = BigDecimal.TEN;
final Currency currency = Currency.AED;
final String paymentExternalKey = UUID.randomUUID().toString();
@@ -1286,9 +1283,6 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
@Test(groups = "slow")
public void testCreateChargebackReversalBeforeChargeback() throws PaymentApiException {
- // API change in 0.17
- final DefaultPaymentApi paymentApi = (DefaultPaymentApi) this.paymentApi;
-
final BigDecimal requestedAmount = BigDecimal.TEN;
final Currency currency = Currency.AED;
final String paymentExternalKey = UUID.randomUUID().toString();
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
index 6b3a0df..386ed00 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -65,9 +65,9 @@ public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAut
}
@Override
- OperationCallback createOperationCallback(final TransactionType transactionType, final PaymentStateControlContext paymentStateContext) {
+ OperationCallback createOperationCallback(final ControlOperation controlOperation, final PaymentStateControlContext paymentStateContext) {
if (operationCallback == null) {
- return super.createOperationCallback(transactionType, paymentStateContext);
+ return super.createOperationCallback(controlOperation, paymentStateContext);
} else {
return operationCallback;
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
index 9f6f8d8..5f87219 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
@@ -42,6 +42,7 @@ import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.core.PaymentExecutors;
import org.killbill.billing.payment.core.PaymentProcessor;
import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
+import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner.ControlOperation;
import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
@@ -61,7 +62,6 @@ import org.killbill.commons.locker.GlobalLock;
import org.killbill.commons.locker.GlobalLocker;
import org.killbill.commons.locker.LockFailedException;
import org.mockito.Mockito;
-import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -232,6 +232,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
try {
runner.run(true,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -272,6 +273,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
runner.run(true,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -306,6 +308,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
runner.run(true,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -340,6 +343,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
try {
runner.run(true,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -378,6 +382,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
try {
runner.run(true,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -415,6 +420,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
try {
runner.run(true,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -452,6 +458,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
try {
runner.run(true,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -496,6 +503,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
runner.run(state,
false,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -546,6 +554,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
runner.run(state,
false,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,
@@ -593,6 +602,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
runner.run(state,
false,
TransactionType.AUTHORIZE,
+ ControlOperation.AUTHORIZE,
account,
paymentMethodId,
null,