Skip to content

Latest commit

 

History

History
212 lines (160 loc) · 7.51 KB

File metadata and controls

212 lines (160 loc) · 7.51 KB

7. Transaction Flow

7.1. Authorization Flow

The authorization flow is the process of approving or declining a transaction. Here's a step-by-step breakdown:

  1. Card presented at merchant
  2. Terminal sends authorization request
  3. Acquirer processes and forwards request
  4. Card network routes to issuer
  5. Issuer approves or declines
  6. 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).

7.2. Clearing and Settlement

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.

7.3. Chargebacks and Disputes

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.