/*
* Copyright 2014 Groupon, Inc
* Copyright 2014 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;
import javax.inject.Inject;
import org.killbill.automaton.MissingEntryException;
import org.killbill.automaton.Operation;
import org.killbill.automaton.OperationResult;
import org.killbill.automaton.State;
import org.killbill.automaton.StateMachine;
import org.killbill.automaton.StateMachineConfig;
import org.killbill.automaton.Transition;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.glue.PaymentModule;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
/**
* This class needs to know about the payment state machine xml file. All the knowledge about the xml file is encapsulated here.
*/
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";
private static final String REFUND_STATE_MACHINE_NAME = "REFUND";
private static final String CREDIT_STATE_MACHINE_NAME = "CREDIT";
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 = "BIG_BANG_INIT";
private static final String AUTHORIZE_SUCCESS = "AUTH_SUCCESS";
private static final String CAPTURE_SUCCESS = "CAPTURE_SUCCESS";
private static final String PURCHASE_SUCCESS = "PURCHASE_SUCCESS";
private static final String REFUND_SUCCESS = "REFUND_SUCCESS";
private static final String CREDIT_SUCCESS = "CREDIT_SUCCESS";
private static final String VOID_SUCCESS = "VOID_SUCCESS";
private static final String CHARGEBACK_SUCCESS = "CHARGEBACK_SUCCESS";
private static final String AUTHORIZE_PENDING = "AUTHORIZE_PENDING";
private static final String AUTHORIZE_FAILED = "AUTH_FAILED";
private static final String CAPTURE_FAILED = "CAPTURE_FAILED";
private static final String PURCHASE_FAILED = "PURCHASE_FAILED";
private static final String REFUND_FAILED = "REFUND_FAILED";
private static final String CREDIT_FAILED = "CREDIT_FAILED";
private static final String VOID_FAILED = "VOID_FAILED";
private static final String CHARGEBACK_FAILED = "CHARGEBACK_FAILED";
private static final String AUTH_ERRORED = "AUTH_ERRORED";
private static final String CAPTURE_ERRORED = "CAPTURE_ERRORED";
private static final String PURCHASE_ERRORED = "PURCHASE_ERRORED";
private static final String REFUND_ERRORED = "REFUND_ERRORED";
private static final String CREDIT_ERRORED = "CREDIT_ERRORED";
private static final String VOID_ERRORED = "VOID_ERRORED";
private static final String CHARGEBACK_ERRORED = "CHARGEBACK_ERRORED";
private final StateMachineConfig stateMachineConfig;
private final String[] errorStateNames = {AUTH_ERRORED, CAPTURE_ERRORED, PURCHASE_ERRORED, REFUND_ERRORED, CREDIT_ERRORED, VOID_ERRORED, CHARGEBACK_ERRORED};
@Inject
public PaymentStateMachineHelper(@javax.inject.Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig) {
this.stateMachineConfig = stateMachineConfig;
}
public State getState(final String stateName) throws MissingEntryException {
final StateMachine stateMachine = stateMachineConfig.getStateMachineForState(stateName);
return stateMachine.getState(stateName);
}
public String getInitStateNameForTransaction() {
return BIG_BANG_INIT;
}
public String getSuccessfulStateForTransaction(final TransactionType transactionType) {
switch (transactionType) {
case AUTHORIZE:
return AUTHORIZE_SUCCESS;
case CAPTURE:
return CAPTURE_SUCCESS;
case PURCHASE:
return PURCHASE_SUCCESS;
case REFUND:
return REFUND_SUCCESS;
case CREDIT:
return CREDIT_SUCCESS;
case VOID:
return VOID_SUCCESS;
case CHARGEBACK:
return CHARGEBACK_SUCCESS;
default:
throw new IllegalStateException("Unsupported transaction type " + transactionType);
}
}
public String getPendingStateForTransaction(final TransactionType transactionType) {
switch (transactionType) {
case AUTHORIZE:
return AUTHORIZE_PENDING;
default:
throw new IllegalStateException("Unsupported transaction type " + transactionType);
}
}
public String getErroredStateForTransaction(final TransactionType transactionType) {
switch (transactionType) {
case AUTHORIZE:
return AUTH_ERRORED;
case CAPTURE:
return CAPTURE_ERRORED;
case PURCHASE:
return PURCHASE_ERRORED;
case REFUND:
return REFUND_ERRORED;
case CREDIT:
return CREDIT_ERRORED;
case VOID:
return VOID_ERRORED;
case CHARGEBACK:
return CHARGEBACK_ERRORED;
default:
throw new IllegalStateException("Unsupported transaction type " + transactionType);
}
}
public String getFailureStateForTransaction(final TransactionType transactionType) {
switch (transactionType) {
case AUTHORIZE:
return AUTHORIZE_FAILED;
case CAPTURE:
return CAPTURE_FAILED;
case PURCHASE:
return PURCHASE_FAILED;
case REFUND:
return REFUND_FAILED;
case CREDIT:
return CREDIT_FAILED;
case VOID:
return VOID_FAILED;
case CHARGEBACK:
return CHARGEBACK_FAILED;
default:
throw new IllegalStateException("Unsupported transaction type " + transactionType);
}
}
public StateMachine getStateMachineForStateName(final String stateName) throws MissingEntryException {
return stateMachineConfig.getStateMachineForState(stateName);
}
public Operation getOperationForTransaction(final TransactionType transactionType) throws MissingEntryException {
final StateMachine stateMachine = getStateMachineForTransaction(transactionType);
// Only one operation defined, this is the current PaymentStates.xml model
return stateMachine.getOperations()[0];
}
public StateMachine getStateMachineForTransaction(final TransactionType transactionType) throws MissingEntryException {
switch (transactionType) {
case AUTHORIZE:
return stateMachineConfig.getStateMachine(AUTHORIZE_STATE_MACHINE_NAME);
case CAPTURE:
return stateMachineConfig.getStateMachine(CAPTURE_STATE_MACHINE_NAME);
case PURCHASE:
return stateMachineConfig.getStateMachine(PURCHASE_STATE_MACHINE_NAME);
case REFUND:
return stateMachineConfig.getStateMachine(REFUND_STATE_MACHINE_NAME);
case CREDIT:
return stateMachineConfig.getStateMachine(CREDIT_STATE_MACHINE_NAME);
case VOID:
return stateMachineConfig.getStateMachine(VOID_STATE_MACHINE_NAME);
case CHARGEBACK:
return stateMachineConfig.getStateMachine(CHARGEBACK_STATE_MACHINE_NAME);
default:
throw new IllegalStateException("Unsupported transaction type " + transactionType + " for null payment id");
}
}
// A better way would be to change the xml to add attributes to the state (e.g isTerminal, isSuccess, isInit,...)
public boolean isSuccessState(final String stateName) {
return stateName.endsWith("SUCCESS");
}
public final State fetchNextState(final String prevStateName, final boolean isSuccess) throws MissingEntryException {
final StateMachine stateMachine = getStateMachineForStateName(prevStateName);
final Transition transition = Iterables.tryFind(ImmutableList.copyOf(stateMachine.getTransitions()), new Predicate<Transition>() {
@Override
public boolean apply(final Transition input) {
// This works because there is only one operation defined for a given state machine, which is our model for PaymentStates.xml
return input.getInitialState().getName().equals(prevStateName) &&
input.getOperationResult().equals(isSuccess ? OperationResult.SUCCESS : OperationResult.FAILURE);
}
}).orNull();
return transition != null ? transition.getFinalState() : null;
}
public String[] getErroredStateNames() {
return errorStateNames;
}
}