Transaction processing is a crucial part of any financial system, especially when dealing with ISO-8583 messages. In jPOS, transaction processing is handled by the TransactionManager and implemented through TransactionParticipants. This approach allows for a modular and flexible way to process transactions.
The TransactionManager is responsible for coordinating the execution of a series of participants for each transaction. It manages the transaction lifecycle, including commit and rollback operations.
Let's implement a basic TransactionManager configuration:
import org.jpos.q2.QBeanSupport;
import org.jpos.transaction.TransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TransactionManagerConfig extends QBeanSupport {
@Bean
public TransactionManager transactionManager() {
TransactionManager txnManager = new TransactionManager();
txnManager.setName("txnManager");
txnManager.setQueue("txnQueue");
txnManager.setMaxSessions(10);
txnManager.setMaxActiveSessions(5);
txnManager.setDebug(true);
return txnManager;
}
}In this configuration, we're setting up a TransactionManager bean with the following properties:
- Name: "txnManager"
- Queue: "txnQueue" (where transactions will be queued)
- MaxSessions: 10 (maximum number of concurrent sessions)
- MaxActiveSessions: 5 (maximum number of active sessions)
- Debug: true (for logging purposes)
TransactionParticipants are the building blocks of transaction processing in jPOS. Each participant performs a specific task in the transaction flow.
Let's create a simple TransactionParticipant that validates a transaction:
import org.jpos.core.Configurable;
import org.jpos.core.Configuration;
import org.jpos.core.ConfigurationException;
import org.jpos.transaction.Context;
import org.jpos.transaction.TransactionParticipant;
public class TransactionValidator implements TransactionParticipant, Configurable {
private Configuration cfg;
@Override
public int prepare(long id, Context context) {
// Validate the transaction
if (isValidTransaction(context)) {
return PREPARED | READONLY;
}
return ABORTED;
}
@Override
public void commit(long id, Context context) {
// Nothing to commit for this participant
}
@Override
public void abort(long id, Context context) {
// Handle abort scenario
context.put("error", "Transaction validation failed");
}
@Override
public void setConfiguration(Configuration cfg) throws ConfigurationException {
this.cfg = cfg;
}
private boolean isValidTransaction(Context context) {
// Implement your validation logic here
// For example, check if required fields are present
return context.get("amount") != null && context.get("cardNumber") != null;
}
}This TransactionValidator checks if the transaction has the required fields (amount and cardNumber in this example).
Now, let's create specific participants for Visa and Mastercard transactions:
public class VisaTransactionParticipant implements TransactionParticipant {
@Override
public int prepare(long id, Context context) {
// Visa-specific validation and processing
if (isValidVisaTransaction(context)) {
context.put("network", "VISA");
return PREPARED;
}
return ABORTED;
}
@Override
public void commit(long id, Context context) {
// Commit Visa transaction
// For example, send to Visa network
}
@Override
public void abort(long id, Context context) {
// Handle Visa transaction abort
}
private boolean isValidVisaTransaction(Context context) {
// Implement Visa-specific validation
String pan = (String) context.get("pan");
return pan != null && pan.startsWith("4");
}
}
public class MastercardTransactionParticipant implements TransactionParticipant {
@Override
public int prepare(long id, Context context) {
// Mastercard-specific validation and processing
if (isValidMastercardTransaction(context)) {
context.put("network", "MASTERCARD");
return PREPARED;
}
return ABORTED;
}
@Override
public void commit(long id, Context context) {
// Commit Mastercard transaction
// For example, send to Mastercard network
}
@Override
public void abort(long id, Context context) {
// Handle Mastercard transaction abort
}
private boolean isValidMastercardTransaction(Context context) {
// Implement Mastercard-specific validation
String pan = (String) context.get("pan");
return pan != null && pan.startsWith("5");
}
}These participants handle Visa and Mastercard specific logic, such as identifying the card network based on the PAN (Primary Account Number) prefix.
To tie everything together, we need to configure the transaction flow. This is typically done in an XML configuration file for jPOS:
<txnmgr class="org.jpos.transaction.TransactionManager" logger="Q2">
<property name="queue" value="txnQueue"/>
<property name="sessions" value="2"/>
<participant class="com.example.TransactionValidator" logger="Q2"/>
<participant class="com.example.VisaTransactionParticipant" logger="Q2"/>
<participant class="com.example.MastercardTransactionParticipant" logger="Q2"/>
<!-- Add more participants as needed -->
</txnmgr>This configuration sets up the TransactionManager and defines the order of participants in the transaction flow.
Let's create a test to verify our transaction flow:
import org.jpos.core.SimpleConfiguration;
import org.jpos.transaction.Context;
import org.jpos.transaction.TransactionManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class TransactionFlowTest {
@Autowired
private TransactionManager transactionManager;
@BeforeEach
public void setup() throws Exception {
// Configure participants
transactionManager.setConfiguration(new SimpleConfiguration());
transactionManager.addParticipant(new TransactionValidator());
transactionManager.addParticipant(new VisaTransactionParticipant());
transactionManager.addParticipant(new MastercardTransactionParticipant());
}
@Test
public void testVisaTransaction() {
Context context = new Context();
context.put("amount", "100.00");
context.put("pan", "4111111111111111");
long id = transactionManager.queue(context);
// Wait for transaction to complete
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
assertThat(context.get("network")).isEqualTo("VISA");
}
@Test
public void testMastercardTransaction() {
Context context = new Context();
context.put("amount", "200.00");
context.put("pan", "5555555555554444");
long id = transactionManager.queue(context);
// Wait for transaction to complete
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
assertThat(context.get("network")).isEqualTo("MASTERCARD");
}
}This test class verifies that our transaction flow correctly identifies Visa and Mastercard transactions based on the PAN.
In conclusion, this implementation demonstrates how to set up transaction processing in jPOS, including the TransactionManager, custom TransactionParticipants for general validation and card network-specific processing, and how to test the transaction flow. This modular approach allows for easy extension and modification of the transaction processing logic as needed.