The authorization flow is the process of approving or declining a transaction. Here's a step-by-step breakdown:
- Card presented at merchant
- Terminal sends authorization request
- Acquirer processes and forwards request
- Card network routes to issuer
- Issuer approves or declines
- Response sent back through the chain
Let's implement a simple authorization flow using jPOS and Spring:
@Service
public class AuthorizationService {
private final QMUX qmux;
@Autowired
public AuthorizationService(QMUX qmux) {
this.qmux = qmux;
}
public ISOMsg processAuthorization(ISOMsg request) throws ISOException, IOException {
// Step 2: Terminal sends authorization request
ISOMsg response = qmux.request(request, 30000); // 30 seconds timeout
// Step 5 & 6: Issuer approves or declines, and response is sent back
if (response == null) {
throw new IOException("No response from issuer");
}
String responseCode = response.getString(39);
if ("00".equals(responseCode)) {
// Approved
System.out.println("Transaction approved");
} else {
// Declined
System.out.println("Transaction declined: " + responseCode);
}
return response;
}
}This service simulates the acquirer's role in processing and forwarding the request. The QMUX component in jPOS handles the routing of the message to the issuer (or a simulator in a test environment).
After the authorization, the clearing and settlement process ensures that funds are transferred between the involved parties. This typically happens in batches at the end of each day.
7.2.1. Batch processing 7.2.2. Clearing files 7.2.3. Settlement between acquirers and issuers
Here's a simple implementation of a batch processor:
@Service
public class BatchProcessingService {
private final TransactionRepository transactionRepository;
private final ClearingFileGenerator clearingFileGenerator;
private final SettlementService settlementService;
@Autowired
public BatchProcessingService(TransactionRepository transactionRepository,
ClearingFileGenerator clearingFileGenerator,
SettlementService settlementService) {
this.transactionRepository = transactionRepository;
this.clearingFileGenerator = clearingFileGenerator;
this.settlementService = settlementService;
}
@Scheduled(cron = "0 0 0 * * *") // Run at midnight every day
public void processDailyBatch() {
LocalDate yesterday = LocalDate.now().minusDays(1);
List<Transaction> transactions = transactionRepository.findByDateBetween(
yesterday.atStartOfDay(),
yesterday.atTime(LocalTime.MAX)
);
// Generate clearing file
File clearingFile = clearingFileGenerator.generateClearingFile(transactions);
// Process settlement
settlementService.processSettlement(clearingFile);
}
}This service runs a daily batch job to process transactions, generate a clearing file, and initiate the settlement process.
Chargebacks occur when a cardholder disputes a transaction. The process typically involves:
7.3.1. Chargeback initiation 7.3.2. Representment 7.3.3. Arbitration
Let's implement a basic chargeback initiation process:
@Service
public class ChargebackService {
private final TransactionRepository transactionRepository;
private final QMUX qmux;
@Autowired
public ChargebackService(TransactionRepository transactionRepository, QMUX qmux) {
this.transactionRepository = transactionRepository;
this.qmux = qmux;
}
public void initiateChargeback(String transactionId, String reason) throws ISOException, IOException {
Transaction transaction = transactionRepository.findById(transactionId)
.orElseThrow(() -> new IllegalArgumentException("Transaction not found"));
ISOMsg chargebackRequest = new ISOMsg();
chargebackRequest.setMTI("0400"); // Reversal message type
chargebackRequest.set(2, transaction.getCardNumber());
chargebackRequest.set(4, transaction.getAmount().toString());
chargebackRequest.set(11, transaction.getSystemTraceAuditNumber());
chargebackRequest.set(37, transaction.getRetrievalReferenceNumber());
chargebackRequest.set(38, transaction.getAuthorizationIdResponse());
chargebackRequest.set(39, "05"); // Chargeback reason code
chargebackRequest.set(56, reason); // Additional chargeback information
ISOMsg response = qmux.request(chargebackRequest, 30000);
if (response != null && "00".equals(response.getString(39))) {
transaction.setStatus(TransactionStatus.CHARGEBACK_INITIATED);
transactionRepository.save(transaction);
} else {
throw new RuntimeException("Chargeback initiation failed");
}
}
}This service initiates a chargeback by creating a reversal message (MTI 0400) with the original transaction details and the chargeback reason.
To test these implementations, we can use JUnit, Mockito, and AssertJ:
@RunWith(SpringRunner.class)
@SpringBootTest
public class AuthorizationServiceTest {
@MockBean
private QMUX qmux;
@Autowired
private AuthorizationService authorizationService;
@Test
public void testSuccessfulAuthorization() throws ISOException, IOException {
// Arrange
ISOMsg request = new ISOMsg();
request.setMTI("0100");
request.set(2, "4111111111111111");
request.set(4, "100000"); // $1000.00
ISOMsg response = new ISOMsg();
response.setMTI("0110");
response.set(39, "00"); // Approval code
when(qmux.request(any(ISOMsg.class), anyLong())).thenReturn(response);
// Act
ISOMsg result = authorizationService.processAuthorization(request);
// Assert
assertThat(result).isNotNull();
assertThat(result.getString(39)).isEqualTo("00");
}
@Test
public void testDeclinedAuthorization() throws ISOException, IOException {
// Arrange
ISOMsg request = new ISOMsg();
request.setMTI("0100");
request.set(2, "4111111111111111");
request.set(4, "100000"); // $1000.00
ISOMsg response = new ISOMsg();
response.setMTI("0110");
response.set(39, "05"); // Do not honor
when(qmux.request(any(ISOMsg.class), anyLong())).thenReturn(response);
// Act
ISOMsg result = authorizationService.processAuthorization(request);
// Assert
assertThat(result).isNotNull();
assertThat(result.getString(39)).isEqualTo("05");
}
}This test class demonstrates how to unit test the AuthorizationService using Mockito to mock the QMUX component and AssertJ for fluent assertions.
In a real-world scenario, you would need to implement more comprehensive error handling, logging, and security measures. Additionally, you'd need to integrate with actual payment networks and comply with PCI-DSS requirements for handling sensitive card data.
The transaction flow in ISO-8583 is complex and involves multiple parties. The examples provided here are simplified for illustration purposes. In a production environment, you would need to handle various edge cases, implement retry mechanisms, and ensure proper security measures are in place.