Visa supports various transaction types, each serving a specific purpose in the payment ecosystem. The main transaction types are:
A standard transaction where a cardholder buys goods or services from a merchant.
A transaction where a cardholder obtains cash using their credit card, typically at an ATM or bank branch.
A non-financial transaction to check the available balance on a card.
A transaction to return funds to a cardholder's account, typically due to a return or cancellation of a purchase.
Visa transactions use specific data elements within the ISO-8583 message structure. Here are some key Visa-specific fields:
This field contains a code that uniquely identifies the card acceptor (merchant) to the acquirer.
This field provides the name and location of the card acceptor (merchant).
This field is used for various purposes, including conveying additional transaction data specific to Visa.
Visa uses specific processing codes to identify the type of transaction being performed. These codes are typically found in Field 3 (Processing Code) of the ISO-8583 message. Here are the main processing codes:
Now, let's implement a Visa-specific transaction processor using Java 21, jPOS, and Spring. This example will focus on creating and processing a Visa purchase transaction.
import org.jpos.iso.ISOException;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.ISOUtil;
import org.jpos.transaction.Context;
import org.jpos.transaction.TransactionParticipant;
import org.springframework.stereotype.Component;
@Component
public class VisaPurchaseProcessor implements TransactionParticipant {
@Override
public int prepare(long id, Context context) {
ISOMsg request = (ISOMsg) context.get("REQUEST");
try {
// Validate that this is a Visa purchase transaction
if (!isVisaPurchase(request)) {
context.put("RESULT", "Invalid transaction type");
return ABORTED;
}
// Create a response message
ISOMsg response = (ISOMsg) request.clone();
response.setMTI("0110"); // Authorization response
// Set Visa-specific fields
setVisaSpecificFields(response);
// Process the transaction (simplified for this example)
boolean approved = processVisaPurchase(request);
if (approved) {
response.set(39, "00"); // Approval code
} else {
response.set(39, "05"); // Do not honor
}
context.put("RESPONSE", response);
return PREPARED;
} catch (ISOException e) {
context.put("RESULT", "Error processing Visa purchase: " + e.getMessage());
return ABORTED;
}
}
@Override
public void commit(long id, Context context) {
// Implement commit logic (e.g., logging, updating database)
}
@Override
public void abort(long id, Context context) {
// Implement abort logic
}
private boolean isVisaPurchase(ISOMsg msg) throws ISOException {
String mti = msg.getMTI();
String processingCode = msg.getString(3);
return "0100".equals(mti) && "000000".equals(processingCode);
}
private void setVisaSpecificFields(ISOMsg msg) throws ISOException {
// Field 42: Card Acceptor ID Code (simplified for example)
msg.set(42, ISOUtil.padright("VISAMERCHANT123", 15, ' '));
// Field 43: Card Acceptor Name/Location (simplified for example)
msg.set(43, ISOUtil.padright("VISA STORE/NEW YORK", 40, ' '));
// Field 48: Additional Data - Private Use (simplified for example)
msg.set(48, "VISA1234567890");
}
private boolean processVisaPurchase(ISOMsg request) {
// Implement actual Visa purchase processing logic here
// This could include fraud checks, balance verification, etc.
// For this example, we'll just return true (approved)
return true;
}
}This VisaPurchaseProcessor class implements the TransactionParticipant interface from jPOS, which allows it to participate in the transaction processing flow. Here's a breakdown of its functionality:
-
The
preparemethod is called when processing a transaction. It checks if the incoming message is a Visa purchase transaction, creates a response message, sets Visa-specific fields, and processes the transaction. -
The
isVisaPurchasemethod verifies that the incoming message is a Visa purchase authorization request (MTI 0100) with the correct processing code (000000 for purchases). -
The
setVisaSpecificFieldsmethod populates Visa-specific fields in the response message, including the Card Acceptor ID Code (field 42), Card Acceptor Name/Location (field 43), and Additional Data - Private Use (field 48). -
The
processVisaPurchasemethod is a placeholder for the actual Visa purchase processing logic. In a real-world scenario, this would include checks for fraud, balance verification, and other necessary validations.
To use this processor in a Spring-based jPOS application, you would configure it in your Spring context and add it to your transaction manager's participant list. Here's an example configuration:
<bean id="visaPurchaseProcessor" class="com.example.VisaPurchaseProcessor"/>
<bean id="txnmgr" class="org.jpos.transaction.TransactionManager">
<property name="queue" value="txnmgr"/>
<property name="sessions" value="2"/>
<property name="participants">
<list>
<ref bean="visaPurchaseProcessor"/>
<!-- Other participants -->
</list>
</property>
</bean>This configuration sets up the VisaPurchaseProcessor as a Spring bean and adds it to the transaction manager's list of participants.
To test this implementation, you can create a JUnit test using Spring Test, JUnit 5, and Mockito:
import org.jpos.iso.ISOMsg;
import org.jpos.iso.ISOUtil;
import org.jpos.transaction.Context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith({SpringExtension.class, MockitoExtension.class})
public class VisaPurchaseProcessorTest {
@InjectMocks
private VisaPurchaseProcessor processor;
private Context context;
private ISOMsg request;
@BeforeEach
void setUp() throws Exception {
context = new Context();
request = new ISOMsg();
request.setMTI("0100");
request.set(3, "000000"); // Processing code for purchase
request.set(4, "000000010000"); // Amount: 100.00
context.put("REQUEST", request);
}
@Test
void testPrepareValidVisaPurchase() throws Exception {
int result = processor.prepare(1L, context);
assertThat(result).isEqualTo(TransactionParticipant.PREPARED);
ISOMsg response = (ISOMsg) context.get("RESPONSE");
assertThat(response).isNotNull();
assertThat(response.getMTI()).isEqualTo("0110");
assertThat(response.getString(39)).isEqualTo("00"); // Approval code
assertThat(response.getString(42)).isEqualTo(ISOUtil.padright("VISAMERCHANT123", 15, ' '));
assertThat(response.getString(43)).isEqualTo(ISOUtil.padright("VISA STORE/NEW YORK", 40, ' '));
assertThat(response.getString(48)).isEqualTo("VISA1234567890");
}
@Test
void testPrepareInvalidTransactionType() throws Exception {
request.set(3, "010000"); // Cash advance instead of purchase
int result = processor.prepare(1L, context);
assertThat(result).isEqualTo(TransactionParticipant.ABORTED);
assertThat(context.get("RESULT")).isEqualTo("Invalid transaction type");
}
}