Skip to content

Commit

Permalink
payment: populate null state machine values in DefaultAdminPaymentApi
Browse files Browse the repository at this point in the history
See discussion killbill/killbill-adyen-plugin#60.

Signed-off-by: Pierre-Alexandre Meyer <pierre@mouraf.org>
  • Loading branch information
pierre committed Dec 2, 2016
1 parent 17307b4 commit 5d078fc
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 8 deletions.
Expand Up @@ -22,39 +22,80 @@


import org.killbill.billing.callcontext.InternalCallContext; import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.payment.core.PaymentTransactionInfoPluginConverter; import org.killbill.billing.payment.core.PaymentTransactionInfoPluginConverter;
import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
import org.killbill.billing.payment.dao.PaymentDao; import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.util.callcontext.CallContext; import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory; import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.config.definition.PaymentConfig; import org.killbill.billing.util.config.definition.PaymentConfig;


public class DefaultAdminPaymentApi extends DefaultApiBase implements AdminPaymentApi { public class DefaultAdminPaymentApi extends DefaultApiBase implements AdminPaymentApi {


private final PaymentStateMachineHelper paymentSMHelper;
private final PaymentDao paymentDao; private final PaymentDao paymentDao;
private final InternalCallContextFactory internalCallContextFactory; private final InternalCallContextFactory internalCallContextFactory;


@Inject @Inject
public DefaultAdminPaymentApi(final PaymentConfig paymentConfig, final PaymentDao paymentDao, final InternalCallContextFactory internalCallContextFactory) { public DefaultAdminPaymentApi(final PaymentConfig paymentConfig,
final PaymentStateMachineHelper paymentSMHelper,
final PaymentDao paymentDao,
final InternalCallContextFactory internalCallContextFactory) {
super(paymentConfig, internalCallContextFactory); super(paymentConfig, internalCallContextFactory);
this.paymentSMHelper = paymentSMHelper;
this.paymentDao = paymentDao; this.paymentDao = paymentDao;
this.internalCallContextFactory = internalCallContextFactory; this.internalCallContextFactory = internalCallContextFactory;
} }


@Override @Override
public void fixPaymentTransactionState(final Payment payment, public void fixPaymentTransactionState(final Payment payment,
final PaymentTransaction paymentTransaction, final PaymentTransaction paymentTransaction,
@Nullable final TransactionStatus transactionStatusMaybeNull, @Nullable final TransactionStatus transactionStatusOrNull,
@Nullable final String lastSuccessPaymentState, @Nullable final String lastSuccessPaymentStateOrNull,
final String currentPaymentStateName, @Nullable final String currentPaymentStateNameOrNull,
final Iterable<PluginProperty> properties, final Iterable<PluginProperty> properties,
final CallContext callContext) throws PaymentApiException { final CallContext callContext) throws PaymentApiException {
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext); final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext);


final TransactionStatus transactionStatus; TransactionStatus transactionStatus = transactionStatusOrNull;
if (transactionStatusMaybeNull == null) { if (transactionStatusOrNull == null) {
checkNotNullParameter(paymentTransaction.getPaymentInfoPlugin(), "PaymentTransactionInfoPlugin"); checkNotNullParameter(paymentTransaction.getPaymentInfoPlugin(), "PaymentTransactionInfoPlugin");
transactionStatus = PaymentTransactionInfoPluginConverter.toTransactionStatus(paymentTransaction.getPaymentInfoPlugin()); transactionStatus = PaymentTransactionInfoPluginConverter.toTransactionStatus(paymentTransaction.getPaymentInfoPlugin());
} else { }
transactionStatus = transactionStatusMaybeNull;
String currentPaymentStateName = currentPaymentStateNameOrNull;
if (currentPaymentStateName == null) {
switch (transactionStatus) {
case PENDING:
currentPaymentStateName = paymentSMHelper.getPendingStateForTransaction(paymentTransaction.getTransactionType());
break;
case SUCCESS:
currentPaymentStateName = paymentSMHelper.getSuccessfulStateForTransaction(paymentTransaction.getTransactionType());
break;
case PAYMENT_FAILURE:
currentPaymentStateName = paymentSMHelper.getFailureStateForTransaction(paymentTransaction.getTransactionType());
break;
case PLUGIN_FAILURE:
case UNKNOWN:
default:
currentPaymentStateName = paymentSMHelper.getErroredStateForTransaction(paymentTransaction.getTransactionType());
break;
}
}

String lastSuccessPaymentState = lastSuccessPaymentStateOrNull;
if (lastSuccessPaymentState == null &&
// Verify we are not updating an older transaction (only the last one has an impact on lastSuccessPaymentState)
paymentTransaction.getId().equals(payment.getTransactions().get(payment.getTransactions().size() - 1).getId())) {
if (paymentSMHelper.isSuccessState(currentPaymentStateName)) {
lastSuccessPaymentState = currentPaymentStateName;
} else {
for (int i = payment.getTransactions().size() - 2; i >= 0; i--) {
final PaymentTransaction transaction = payment.getTransactions().get(i);
if (TransactionStatus.SUCCESS.equals(transaction.getTransactionStatus())) {
lastSuccessPaymentState = paymentSMHelper.getSuccessfulStateForTransaction(transaction.getTransactionType());
break;
}
}
}
} }


paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(), paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(),
Expand Down
Expand Up @@ -206,4 +206,98 @@ public void testFixPaymentTransactionStateFromPaymentTransactionInfoPlugin() thr
Assert.assertEquals(refreshedPaymentTransactionModelDao.getGatewayErrorCode(), "error-code"); Assert.assertEquals(refreshedPaymentTransactionModelDao.getGatewayErrorCode(), "error-code");
Assert.assertEquals(refreshedPaymentTransactionModelDao.getGatewayErrorMsg(), "error-msg"); Assert.assertEquals(refreshedPaymentTransactionModelDao.getGatewayErrorMsg(), "error-msg");
} }

@Test(groups = "slow", description = "https://github.com/killbill/killbill-adyen-plugin/pull/60")
public void testFixPaymentTransactionStateRefundSuccessToRefundFailed() throws PaymentApiException {
final Payment payment = paymentApi.createPurchase(account,
account.getPaymentMethodId(),
null,
BigDecimal.TEN,
Currency.EUR,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
callContext);

final PaymentModelDao paymentModelDao = paymentDao.getPayment(payment.getId(), internalCallContext);
final PaymentTransactionModelDao paymentTransactionModelDao = paymentDao.getPaymentTransaction(payment.getTransactions().get(0).getId(), internalCallContext);
Assert.assertEquals(paymentModelDao.getStateName(), "PURCHASE_SUCCESS");
Assert.assertEquals(paymentModelDao.getLastSuccessStateName(), "PURCHASE_SUCCESS");
Assert.assertEquals(paymentTransactionModelDao.getTransactionStatus(), TransactionStatus.SUCCESS);

final Payment refund = paymentApi.createRefund(account,
payment.getId(),
payment.getPurchasedAmount(),
payment.getCurrency(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
callContext);

final PaymentModelDao paymentModelDao2 = paymentDao.getPayment(payment.getId(), internalCallContext);
final PaymentTransactionModelDao paymentTransactionModelDao2 = paymentDao.getPaymentTransaction(refund.getTransactions().get(1).getId(), internalCallContext);
Assert.assertEquals(paymentModelDao2.getStateName(), "REFUND_SUCCESS");
Assert.assertEquals(paymentModelDao2.getLastSuccessStateName(), "REFUND_SUCCESS");
Assert.assertEquals(paymentTransactionModelDao2.getTransactionStatus(), TransactionStatus.SUCCESS);

adminPaymentApi.fixPaymentTransactionState(refund,
refund.getTransactions().get(1),
TransactionStatus.PAYMENT_FAILURE,
null, /* Let Kill Bill figure it out */
null, /* Let Kill Bill figure it out */
ImmutableList.<PluginProperty>of(),
callContext);

final PaymentModelDao paymentModelDao3 = paymentDao.getPayment(payment.getId(), internalCallContext);
final PaymentTransactionModelDao paymentTransactionModelDao3 = paymentDao.getPaymentTransaction(refund.getTransactions().get(1).getId(), internalCallContext);
Assert.assertEquals(paymentModelDao3.getStateName(), "REFUND_FAILED");
Assert.assertEquals(paymentModelDao3.getLastSuccessStateName(), "PURCHASE_SUCCESS");
Assert.assertEquals(paymentTransactionModelDao3.getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
}

@Test(groups = "slow", description = "https://github.com/killbill/killbill-adyen-plugin/pull/60")
public void testFixPaymentTransactionStateRefundFailedToRefundSuccess() throws PaymentApiException {
final Payment payment = paymentApi.createPurchase(account,
account.getPaymentMethodId(),
null,
BigDecimal.TEN,
Currency.EUR,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
callContext);

final PaymentModelDao paymentModelDao = paymentDao.getPayment(payment.getId(), internalCallContext);
final PaymentTransactionModelDao paymentTransactionModelDao = paymentDao.getPaymentTransaction(payment.getTransactions().get(0).getId(), internalCallContext);
Assert.assertEquals(paymentModelDao.getStateName(), "PURCHASE_SUCCESS");
Assert.assertEquals(paymentModelDao.getLastSuccessStateName(), "PURCHASE_SUCCESS");
Assert.assertEquals(paymentTransactionModelDao.getTransactionStatus(), TransactionStatus.SUCCESS);

final Payment refund = paymentApi.createRefund(account,
payment.getId(),
payment.getPurchasedAmount(),
payment.getCurrency(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.ERROR.toString(), false)),
callContext);

final PaymentModelDao paymentModelDao2 = paymentDao.getPayment(payment.getId(), internalCallContext);
final PaymentTransactionModelDao paymentTransactionModelDao2 = paymentDao.getPaymentTransaction(refund.getTransactions().get(1).getId(), internalCallContext);
Assert.assertEquals(paymentModelDao2.getStateName(), "REFUND_FAILED");
Assert.assertEquals(paymentModelDao2.getLastSuccessStateName(), "PURCHASE_SUCCESS");
Assert.assertEquals(paymentTransactionModelDao2.getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);

adminPaymentApi.fixPaymentTransactionState(refund,
refund.getTransactions().get(1),
TransactionStatus.SUCCESS,
null, /* Let Kill Bill figure it out */
null, /* Let Kill Bill figure it out */
ImmutableList.<PluginProperty>of(),
callContext);

final PaymentModelDao paymentModelDao3 = paymentDao.getPayment(payment.getId(), internalCallContext);
final PaymentTransactionModelDao paymentTransactionModelDao3 = paymentDao.getPaymentTransaction(refund.getTransactions().get(1).getId(), internalCallContext);
Assert.assertEquals(paymentModelDao3.getStateName(), "REFUND_SUCCESS");
Assert.assertEquals(paymentModelDao3.getLastSuccessStateName(), "REFUND_SUCCESS");
Assert.assertEquals(paymentTransactionModelDao3.getTransactionStatus(), TransactionStatus.SUCCESS);
}
} }

2 comments on commit 5d078fc

@pierre
Copy link
Member Author

@pierre pierre commented on 5d078fc Dec 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @andrenpaes for review.

It might be worth thinking about other odd cases we get with Adyen and add more tests.

@sbrossie
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.