diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index 49d48f6..eeeaba6 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -46,7 +46,6 @@ import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentModelDao;
-import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
import org.killbill.billing.payment.dispatcher.PluginDispatcher;
import org.killbill.billing.payment.glue.PaymentModule;
import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
@@ -112,7 +111,7 @@ public class PaymentAutomatonRunner {
if (paymentId != null) {
final PaymentModelDao paymentModelDao = daoHelper.getPayment();
effectivePaymentMethodId = paymentModelDao.getPaymentMethodId();
- currentStateName = paymentModelDao.getLastSuccessStateName() != null ? paymentModelDao.getLastSuccessStateName() : paymentSMHelper.getInitStateNameForTransaction(transactionType);
+ currentStateName = paymentModelDao.getLastSuccessStateName() != null ? paymentModelDao.getLastSuccessStateName() : paymentSMHelper.getInitStateNameForTransaction();
// Check for illegal states (should never happen)
Preconditions.checkState(currentStateName != null, "State name cannot be null for payment " + paymentId);
@@ -121,7 +120,7 @@ public class PaymentAutomatonRunner {
// If the payment method is not specified, retrieve the default one on the account; it could still be null, in which case
//
effectivePaymentMethodId = paymentMethodId != null ? paymentMethodId : account.getPaymentMethodId();
- currentStateName = paymentSMHelper.getInitStateNameForTransaction(transactionType);
+ currentStateName = paymentSMHelper.getInitStateNameForTransaction();
}
paymentStateContext.setPaymentMethodId(effectivePaymentMethodId);
@@ -183,7 +182,7 @@ public class PaymentAutomatonRunner {
initialState.runOperation(operation, operationCallback, enteringStateCallback, leavingStateCallback);
} catch (final MissingEntryException e) {
- throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+ throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INVALID_OPERATION, transactionType, initialStateName);
} catch (final OperationException e) {
if (e.getCause() == null) {
throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
index 009d70b..763ec9c 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
@@ -35,6 +35,7 @@ import com.google.common.collect.Iterables;
public class PaymentStateMachineHelper {
+ private static final String BIG_BANG_STATE_MACHINE_NAME = "BIG_BANG";
private static final String AUTHORIZE_STATE_MACHINE_NAME = "AUTHORIZE";
private static final String CAPTURE_STATE_MACHINE_NAME = "CAPTURE";
private static final String PURCHASE_STATE_MACHINE_NAME = "PURCHASE";
@@ -43,6 +44,8 @@ public class PaymentStateMachineHelper {
private static final String VOID_STATE_MACHINE_NAME = "VOID";
private static final String CHARGEBACK_STATE_MACHINE_NAME = "CHARGEBACK";
+
+ private static final String BIG_BANG_INIT_STATE_NAME = "BIG_BANG_INIT";
private static final String AUTHORIZE_INIT_STATE_NAME = "AUTH_INIT";
private static final String CAPTURE_INIT_STATE_NAME = "CAPTURE_INIT";
private static final String PURCHASE_INIT_STATE_NAME = "PURCHASE_INIT";
@@ -63,25 +66,8 @@ public class PaymentStateMachineHelper {
return stateMachine.getState(stateName);
}
- public String getInitStateNameForTransaction(final TransactionType transactionType) {
- switch (transactionType) {
- case AUTHORIZE:
- return AUTHORIZE_INIT_STATE_NAME;
- case CAPTURE:
- return CAPTURE_INIT_STATE_NAME;
- case PURCHASE:
- return PURCHASE_INIT_STATE_NAME;
- case REFUND:
- return REFUND_INIT_STATE_NAME;
- case CREDIT:
- return CREDIT_INIT_STATE_NAME;
- case VOID:
- return VOID_INIT_STATE_NAME;
- case CHARGEBACK:
- return CHARGEBACK_INIT_STATE_NAME;
- default:
- throw new IllegalStateException("Unsupported transaction type " + transactionType + " for null payment id");
- }
+ public String getInitStateNameForTransaction() {
+ return BIG_BANG_INIT_STATE_NAME;
}
public StateMachine getStateMachineForStateName(final String stateName) throws MissingEntryException {
diff --git a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
index c6f32d3..f4aeef9 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
+++ b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
@@ -19,6 +19,22 @@
xsi:noNamespaceSchemaLocation="StateMachineConfig.xsd">
<stateMachines>
+ <stateMachine name="BIG_BANG">
+ <states>
+ <state name="BIG_BANG_INIT"/>
+ </states>
+ <transitions>
+ <transition>
+ <initialState>BIG_BANG_INIT</initialState>
+ <operation>OP_DUMMY</operation>
+ <operationResult>SUCCESS</operationResult>
+ <finalState>BIG_BANG_INIT</finalState>
+ </transition>
+ </transitions>
+ <operations>
+ <operation name="OP_DUMMY"/>
+ </operations>
+ </stateMachine>
<stateMachine name="AUTHORIZE">
<states>
<state name="AUTH_INIT"/>
@@ -270,6 +286,24 @@
<linkStateMachines>
<linkStateMachine>
+ <initialStateMachine>BIG_BANG</initialStateMachine>
+ <initialState>BIG_BANG_INIT</initialState>
+ <finalStateMachine>AUTHORIZE</finalStateMachine>
+ <finalState>AUTH_INIT</finalState>
+ </linkStateMachine>
+ <linkStateMachine>
+ <initialStateMachine>BIG_BANG</initialStateMachine>
+ <initialState>BIG_BANG_INIT</initialState>
+ <finalStateMachine>PURCHASE</finalStateMachine>
+ <finalState>PURCHASE_INIT</finalState>
+ </linkStateMachine>
+ <linkStateMachine>
+ <initialStateMachine>BIG_BANG</initialStateMachine>
+ <initialState>BIG_BANG_INIT</initialState>
+ <finalStateMachine>CREDIT</finalStateMachine>
+ <finalState>CREDIT_INIT</finalState>
+ </linkStateMachine>
+ <linkStateMachine>
<initialStateMachine>AUTHORIZE</initialStateMachine>
<initialState>AUTH_SUCCESS</initialState>
<finalStateMachine>AUTHORIZE</finalStateMachine>
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 6ef4484..ce075e3 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
@@ -35,6 +35,7 @@ import org.killbill.billing.payment.MockRecurringInvoiceItem;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.dao.PaymentSqlDao;
import org.killbill.billing.retry.plugin.api.PaymentControlApiException;
import org.killbill.bus.api.PersistentBus.EventBusException;
import org.testng.Assert;
@@ -78,7 +79,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "krapaut";
final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
- ImmutableList.<PluginProperty>of(), callContext);
+ ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment.getExternalKey(), paymentExternalKey);
assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
@@ -559,6 +560,32 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
}
}
+ @Test(groups = "slow")
+ public void testInvalidTransitionAfterFailure() throws PaymentApiException {
+
+ final BigDecimal requestedAmount = BigDecimal.TEN;
+
+ final String paymentExternalKey = "krapo";
+ final String transactionExternalKey = "grenouye";
+
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.EUR, paymentExternalKey, transactionExternalKey,
+ ImmutableList.<PluginProperty>of(), callContext);
+
+ // Hack the Database to make it look like it was a failure
+ paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), payment.getId(), TransactionType.AUTHORIZE, "AUTH_ERRORED", null,
+ payment.getTransactions().get(0).getId(), TransactionStatus.PLUGIN_FAILURE, null, null, null, null, internalCallContext);
+ PaymentSqlDao paymentSqlDao = dbi.onDemand(PaymentSqlDao.class);
+ paymentSqlDao.updateLastSuccessPaymentStateName(payment.getId().toString(), "AUTH_ERRORED", null, internalCallContext);
+
+ try {
+ paymentApi.createCapture(account, payment.getId(), requestedAmount, Currency.EUR, "tetard", ImmutableList.<PluginProperty>of(), callContext);
+ Assert.fail("Unexpected success");
+ } catch (PaymentApiException e){
+ Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+ }
+ }
+
+
private List<PluginProperty> createPropertiesForInvoice(final Invoice invoice) {