Skip to content

Commit

Permalink
Create mock Airtel APIs.
Browse files Browse the repository at this point in the history
The response codes in the `application.yml` file have been added from the Airtel documentation.

Similarly, the responses returned by the API are based on the Airtel documentation.
  • Loading branch information
sanchit-0 committed Aug 6, 2024
1 parent fa62a65 commit 2f62aad
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 3 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.mifos:ph-ee-connector-common:1.9.1-SNAPSHOT'
implementation 'org.mifos:ph-ee-connector-common:0.0.0'
implementation 'org.json:json:20210307'
checkstyle 'com.puppycrawl.tools:checkstyle:10.9.3'
checkstyle 'com.github.sevntu-checkstyle:sevntu-checks:1.44.1'
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/mifos/connector/airtel/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.mifos.connector.airtel;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.mifos.connector.airtel.api.definition;

import org.mifos.connector.airtel.api.implementation.CallBackController;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelCallBackRequestDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CallBackApi {

@Autowired
CallBackController callBackController;

@PostMapping("/callback")
public ResponseEntity<AirtelCallBackRequestDTO> getCallBack(@RequestBody AirtelCallBackRequestDTO requestBody) {
return callBackController.handleCallBackRequest(requestBody);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.mifos.connector.airtel.api.implementation;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelCallBackRequestDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

@Service
public class CallBackController {

@Autowired
ObjectMapper objectMapper;

private final Logger logger = LoggerFactory.getLogger(this.getClass());

public ResponseEntity<AirtelCallBackRequestDTO> handleCallBackRequest(AirtelCallBackRequestDTO requestBody) {
try {
logger.info("CallBack Request: {}", objectMapper.writeValueAsString(requestBody));
return ResponseEntity.status(HttpStatus.OK).body(requestBody);
} catch (Exception ex) {
throw new RuntimeException("Invalid response!", ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.mifos.connector.airtel.mockairtel.api.definition;

import org.mifos.connector.airtel.mockairtel.api.implementation.AirtelMockController;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelCallBackRequestDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelEnquiryResponseDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelPaymentRequestDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelPaymentResponseDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AirtelMockApi {

@Autowired
private AirtelMockController airtelMockController;

protected Logger logger = LoggerFactory.getLogger(this.getClass());

@GetMapping(value = "/standard/v1/payments/{transactionId}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<AirtelEnquiryResponseDTO> airtelTransactionEnquiry(@PathVariable String transactionId) {
return airtelMockController.getTransactionStatus(transactionId);
}

@PostMapping("/merchant/v2/payments")
public ResponseEntity<AirtelPaymentResponseDTO> getAuthorization(@RequestHeader(value = "X-Country") String country,
@RequestHeader(value = "X-Currency") String currency, @RequestHeader(value = "Authorization") String authorization,
@RequestHeader(value = "x-signature") String signature, @RequestHeader(value = "x-key") String key,
@RequestBody AirtelPaymentRequestDTO airtelPaymentRequestDTO) {
return airtelMockController.initiateTransaction(airtelPaymentRequestDTO);
}

@PostMapping("/sendcallback")
public ResponseEntity<AirtelCallBackRequestDTO> sendCallback(@RequestBody String transactionId) {
return airtelMockController.sendCallBack(transactionId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package org.mifos.connector.airtel.mockairtel.api.implementation;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.UUID;
import org.mifos.connector.airtel.mockairtel.utils.TransferStatus;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelCallBackRequestDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelCallBackRequestTransactionDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelEnquiryResponseDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelEnquiryResponseDataDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelEnquiryResponseDataTransactionDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelPaymentRequestDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelPaymentResponseDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelPaymentResponseDataDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelPaymentResponseDataTransactionDTO;
import org.mifos.connector.common.mobilemoney.airtel.dto.AirtelResponseStatusDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class AirtelMockController {

@Autowired
ObjectMapper objectMapper;

@Value("${airtel.endpoints.contact-point}")
public String airtelContactPoint;

@Value("${server.port}")
public String port;

@Value("${airtel.endpoints.call-back}")
public String callBackEndpoint;

@Value("${airtel.endpoints.send-call-back}")
public String sendCallBackEndpoint;

@Value("${mock-airtel.MSISDN_FAILED}")
private String msisdnFailed;

@Value("${mock-airtel.CLIENT_CORRELATION_ID_SUCCESSFUL}")
private String transactionIdSuccessful;

@Value("${mock-airtel.CLIENT_CORRELATION_ID_FAILED}")
private String transactionIdFailed;

@Value("${mock-airtel.SUCCESS_RESPONSE_CODE}")
private String successResponseCode;

@Value("${mock-airtel.FAILED_RESPONSE_CODE}")
private String failedResponseCode;

@Value("${mock-airtel.PENDING_RESPONSE_CODE}")
private String pendingResponseCode;

@Value("${mock-airtel.RESULT_CODE}")
private String resultCode;

private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RestTemplate restTemplate;

public ResponseEntity<AirtelEnquiryResponseDTO> getTransactionStatus(String transactionId) {
String airtelMoneyId = UUID.nameUUIDFromBytes(transactionId.getBytes()).toString().replace("-", "");
String message;
String status;
String code;
String responseCode;
boolean success;
HttpStatus httpStatus;

if (transactionId.equals(transactionIdSuccessful)) {
message = TransferStatus.SUCCESS.name();
status = TransferStatus.TS.name();
code = HttpStatus.OK.toString();
responseCode = successResponseCode;
success = true;
httpStatus = HttpStatus.OK;
}

else if (transactionId.equals(transactionIdFailed)) {
message = TransferStatus.FAILED.name();
status = TransferStatus.TF.name();
code = HttpStatus.BAD_REQUEST.toString();
responseCode = failedResponseCode;
success = false;
httpStatus = HttpStatus.BAD_REQUEST;
}

else {
message = TransferStatus.IN_PROGRESS.name();
status = TransferStatus.TIP.name();
code = HttpStatus.ACCEPTED.toString();
responseCode = pendingResponseCode;
success = true;
httpStatus = HttpStatus.ACCEPTED;
}

return airtelEnquiryResponse(airtelMoneyId, transactionId, message, status, code, responseCode, success, httpStatus);
}

public ResponseEntity<AirtelPaymentResponseDTO> initiateTransaction(AirtelPaymentRequestDTO airtelPaymentRequestDTO) {
String message;
String status;
String responseCode;
boolean success;
String code = HttpStatus.OK.name();
HttpStatus httpStatus = HttpStatus.OK;

String msisdn = airtelPaymentRequestDTO.getSubscriber().getMsisdn();

if (msisdn.equals(msisdnFailed)) {
message = TransferStatus.FAILED.name();
responseCode = failedResponseCode;
success = false;
AirtelResponseStatusDTO airtelResponseStatusDTO = new AirtelResponseStatusDTO(code, message, resultCode, responseCode, success);
AirtelPaymentResponseDTO responseEntity = new AirtelPaymentResponseDTO(null, airtelResponseStatusDTO);
return ResponseEntity.status(httpStatus).body(responseEntity);
}

else {
message = TransferStatus.IN_PROGRESS.name();
status = TransferStatus.IN_PROGRESS.name();
responseCode = pendingResponseCode;
success = true;

String url = airtelContactPoint + ":" + port + sendCallBackEndpoint;

String transactionId = airtelPaymentRequestDTO.getTransaction().getId();
restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(transactionId, new HttpHeaders()), AirtelCallBackRequestDTO.class);
}

boolean id = false;
AirtelPaymentResponseDataTransactionDTO airtelPaymentResponseDataTransactionDTO = new AirtelPaymentResponseDataTransactionDTO(id,
status);
AirtelPaymentResponseDataDTO airtelResponseDataDTO = new AirtelPaymentResponseDataDTO(airtelPaymentResponseDataTransactionDTO);
AirtelResponseStatusDTO airtelResponseStatusDTO = new AirtelResponseStatusDTO(code, message, resultCode, responseCode, success);
AirtelPaymentResponseDTO responseEntity = new AirtelPaymentResponseDTO(airtelResponseDataDTO, airtelResponseStatusDTO);
return ResponseEntity.status(httpStatus).body(responseEntity);
}

private ResponseEntity<AirtelEnquiryResponseDTO> airtelEnquiryResponse(String airtelMoneyId, String transactionId, String message,
String status, String code, String responseCode, Boolean success, HttpStatus httpStatus) {
String id = transactionId;

AirtelEnquiryResponseDataTransactionDTO airtelEnquiryResponseDataTransactionDTO = new AirtelEnquiryResponseDataTransactionDTO(
airtelMoneyId, id, message, status);
AirtelEnquiryResponseDataDTO airtelResponseDataDTO = new AirtelEnquiryResponseDataDTO(airtelEnquiryResponseDataTransactionDTO);

AirtelResponseStatusDTO airtelResponseStatusDTO = new AirtelResponseStatusDTO(code, message, resultCode, responseCode, success);

AirtelEnquiryResponseDTO responseEntity = new AirtelEnquiryResponseDTO(airtelResponseDataDTO, airtelResponseStatusDTO);
return ResponseEntity.status(httpStatus).body(responseEntity);
}

@Async
public ResponseEntity<AirtelCallBackRequestDTO> sendCallBack(String transactionId) {
String url = airtelContactPoint + ":" + port + callBackEndpoint;
HttpHeaders headers = new HttpHeaders();
String airtelMoneyId = UUID.nameUUIDFromBytes(transactionId.getBytes()).toString().replace("-", "");
String statusCode = TransferStatus.TF.name();

if (transactionId.equals(transactionIdSuccessful)) {
statusCode = TransferStatus.TS.name();
}

AirtelCallBackRequestTransactionDTO transactionDTO = new AirtelCallBackRequestTransactionDTO();
transactionDTO.setId(transactionId);
transactionDTO.setMessage("Paid amount x");
transactionDTO.setStatusCode(statusCode);
transactionDTO.setAirtelMoneyId(airtelMoneyId);

AirtelCallBackRequestDTO callBackRequestDTO = new AirtelCallBackRequestDTO();
callBackRequestDTO.setTransaction(transactionDTO);
try {
// Sleep for 1 second before sending callback
Thread.sleep(1000);
ResponseEntity<AirtelCallBackRequestDTO> result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(callBackRequestDTO, headers), AirtelCallBackRequestDTO.class);
HttpStatus statusCode2 = result.getStatusCode();
logger.info("Response code from sendcallback: {}", statusCode2.value());
logger.info("Response sendcallback: {}", objectMapper.writeValueAsString(result));
return result;
} catch (Exception ex) {
throw new RuntimeException("Invalid response!", ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.mifos.connector.airtel.mockairtel.utils;

public enum TransferStatus {
COMPLETED, FAILED, IN_PROGRESS, UNKNOWN, SUCCESS, TS, TF, TIP
}
25 changes: 23 additions & 2 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
dfspids: "DFSPID"

server:
port: 8080

transaction-id-length: -1

timer: "PT45S"
Expand All @@ -13,15 +16,33 @@ operations:

bpmn:
flows:
airtel_flow_mifos: "airtel_flow_mifos-{dfspid}"
AIRTEL_FLOW_MIFOS: "airtel_flow_mifos-{dfspid}"

ams:
groups:
- identifier: "accountid"
- identifier: "account_id"
value: "fineract"
- identifier: "default"
value : "fineract"

airtel:
MAX_RETRY_COUNT: 3
endpoints:
contact-point: "http://localhost"
airtel-ussd-push: "/merchant/v2/payments/"
airtel-transaction-enquiry: "/standard/v1/payments/"
call-back: "/callback"
send-call-back: "/sendcallback"

mock-airtel:
FAILED_RESPONSE_CODE: "DP00800001005"
SUCCESS_RESPONSE_CODE: "DP00800001001"
PENDING_RESPONSE_CODE: "DP00800001006"
MSISDN_SUCCESSFUL: "1643344477"
MSISDN_FAILED: "6729461912"
CLIENT_CORRELATION_ID_SUCCESSFUL: "123456"
CLIENT_CORRELATION_ID_FAILED: "1278320"
RESULT_CODE: "ESB000010"

logging:
level:
Expand Down

0 comments on commit 2f62aad

Please sign in to comment.