Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.mifos.connector.channel.api.definition;

import static org.mifos.connector.channel.camel.config.CamelProperties.CALLBACK_URL;
import static org.mifos.connector.channel.camel.config.CamelProperties.CLIENTCORRELATIONID;
import static org.mifos.connector.channel.zeebe.ZeebeVariables.TRANSACTION_ID;

Expand All @@ -25,6 +26,7 @@ public interface TransactionApi {
@PostMapping("/channel/transactionRequest")
ResponseEntity<GsmaP2PResponseDto> transaction(@RequestHeader(value = "Platform-TenantId") String tenant,
@RequestHeader(value = CLIENTCORRELATIONID, required = false) String correlationId,
@RequestHeader(value = CALLBACK_URL, required = false) String callbackURL,
@RequestBody TransactionChannelRequestDTO requestBody) throws JsonProcessingException;

@PostMapping("/channel/transaction/{" + TRANSACTION_ID + "}/resolve")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.camunda.zeebe.client.api.command.ClientStatusException;
import io.grpc.Status;
import java.util.Objects;
import org.apache.camel.Exchange;
import org.apache.camel.ProducerTemplate;
import org.mifos.connector.channel.api.definition.TransactionApi;
import org.mifos.connector.channel.camel.routes.ChannelRouteBuilder;
import org.mifos.connector.channel.gsma_api.GsmaP2PResponseDto;
import org.mifos.connector.channel.service.ValidateHeaders;
import org.mifos.connector.channel.utils.HeaderConstants;
Expand All @@ -19,6 +21,7 @@
import org.mifos.connector.common.channel.dto.TransactionChannelRequestDTO;
import org.mifos.connector.common.exception.ValidationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -30,12 +33,16 @@ public class TransactionApiController implements TransactionApi {
ObjectMapper objectMapper;
@Autowired
private ProducerTemplate producerTemplate;
@Autowired
ChannelRouteBuilder routeBuilder;
@Value("${mtn-transaction-request.tenant}")
private String mtnTenant;

@Override
@ValidateHeaders(requiredHeaders = { HeaderConstants.PLATFORM_TENANT_ID,
HeaderConstants.CLIENT_CORRELATION_ID }, validatorClass = HeaderValidator.class, validationFunction = "validateTransactionRequest")
public ResponseEntity<GsmaP2PResponseDto> transaction(String tenant, String correlationId, TransactionChannelRequestDTO requestBody)
throws JsonProcessingException {
@ValidateHeaders(requiredHeaders = { HeaderConstants.PLATFORM_TENANT_ID, HeaderConstants.CLIENT_CORRELATION_ID,
HeaderConstants.X_Callback_URL }, validatorClass = HeaderValidator.class, validationFunction = "validateTransactionRequest")
public ResponseEntity<GsmaP2PResponseDto> transaction(String tenant, String correlationId, String callbackURL,
TransactionChannelRequestDTO requestBody) throws JsonProcessingException {

try {
ChannelValidator.validateTransfer(requestBody);
Expand All @@ -45,6 +52,13 @@ public ResponseEntity<GsmaP2PResponseDto> transaction(String tenant, String corr

Headers headers = new Headers.HeaderBuilder().addHeader("Platform-TenantId", tenant).addHeader(CLIENTCORRELATIONID, correlationId)
.build();

if (Objects.equals(tenant, mtnTenant)) {
String transactionId = routeBuilder.mtnTxn(requestBody, mtnTenant, callbackURL);
GsmaP2PResponseDto responseDto = new GsmaP2PResponseDto(transactionId);
return ResponseEntity.status(HttpStatus.ACCEPTED).body(responseDto);
}

Exchange exchange = SpringWrapperUtil.getDefaultWrappedExchange(producerTemplate.getCamelContext(), headers,
objectMapper.writeValueAsString(requestBody));
producerTemplate.send("direct:post-transaction-request", exchange);
Expand All @@ -64,6 +78,5 @@ public void transactionResolve(String requestBody) throws JsonProcessingExceptio
Headers headers = new Headers.HeaderBuilder().build();
Exchange exchange = SpringWrapperUtil.getDefaultWrappedExchange(producerTemplate.getCamelContext(), null, requestBody);
producerTemplate.send("direct:post-transaction-resolve", exchange);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ private CamelProperties() {}
public static final String PAYEE_DFSP_ID = "X-PayeeDFSP-ID";
public static final String X_CALLBACKURL = "X-CallbackURL";
public static final String COUNTRY = "X-Country";
public static final String CALLBACK_URL = "X-CallbackURL";

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.mifos.connector.channel.utils.AMSProps;
import org.mifos.connector.channel.utils.AMSUtils;
import org.mifos.connector.channel.utils.Constants;
import org.mifos.connector.channel.utils.HeaderConstants;
import org.mifos.connector.channel.zeebe.ZeebeProcessStarter;
import org.mifos.connector.common.camel.AuthProcessor;
import org.mifos.connector.common.camel.AuthProperties;
Expand Down Expand Up @@ -103,6 +104,7 @@ public class ChannelRouteBuilder extends ErrorHandlerRouteBuilder {
private String paymentTransferFlow;
private String specialPaymentTransferFlow;
private String transactionRequestFlow;
private String mtnTransactionRequestFlow;
private String partyRegistration;
private String inboundTransactionReqFlow;
private String restAuthHost;
Expand All @@ -127,6 +129,7 @@ public ChannelRouteBuilder(@Value("#{'${dfspids}'.split(',')}") List<String> dfs
@Value("${bpmn.flows.payment-transfer}") String paymentTransferFlow,
@Value("${bpmn.flows.special-payment-transfer}") String specialPaymentTransferFlow,
@Value("${bpmn.flows.transaction-request}") String transactionRequestFlow,
@Value("${bpmn.flows.mtn-transaction-request}") String mtnTransactionRequestFlow,
@Value("${bpmn.flows.party-registration}") String partyRegistration,
@Value("${bpmn.flows.inboundTransactionReq-flow}") String inboundTransactionReqFlow,
@Value("${rest.authorization.host}") String restAuthHost, @Value("${operations.url}") String operationsUrl,
Expand Down Expand Up @@ -163,6 +166,7 @@ public ChannelRouteBuilder(@Value("#{'${dfspids}'.split(',')}") List<String> dfs
this.restAuthHeader = restAuthHeader;
this.operationsAuthEnabled = operationsAuthEnabled;
this.destinationDfspId = destinationDfspId;
this.mtnTransactionRequestFlow = mtnTransactionRequestFlow;
}

@Override
Expand Down Expand Up @@ -374,6 +378,19 @@ private void transferRoutes() {
});
}

public String mtnTxn(TransactionChannelRequestDTO requestBody, String tenantId, String callbackURL) throws JsonProcessingException {
Map<String, Object> extraVariables = new HashMap<>();
String body = objectMapper.writeValueAsString(requestBody);
extraVariables.put(HeaderConstants.X_Callback_URL, callbackURL);

String tenantSpecificBpmn = mtnTransactionRequestFlow.replace("{dfspid}", tenantId);

String transactionId = zeebeProcessStarter.startZeebeWorkflow(tenantSpecificBpmn, body, extraVariables);

logger.debug("transactionId is : {}", transactionId);
return transactionId;
}

public ResponseEntity<String> fetchApibyWorkflowKey(HttpEntity<String> entity, long workflowInstanceKey) {
return restTemplate.exchange(operationsUrl + "/transfer/" + workflowInstanceKey, HttpMethod.GET, entity, String.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package org.mifos.connector.channel.gsma_api;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
public class GsmaP2PResponseDto {

public String transactionId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.mifos.connector.channel.utils.ChannelValidatorsEnum;
import org.mifos.connector.channel.utils.HeaderConstants;
import org.mifos.connector.common.channel.dto.PhErrorDTO;
Expand All @@ -16,6 +17,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class HeaderValidator {

Expand All @@ -25,6 +27,9 @@ public class HeaderValidator {
@Value("#{'${default_headers}'.split(',')}")
private List<String> defaultHeader;

@Value("${mtn-transaction-request.tenant}")
private String mtnTenant;

private static final String resource = "channelValidator";

public PhErrorDTO validateTransfer(Set<String> requiredHeaders, HttpServletRequest request) {
Expand Down Expand Up @@ -54,6 +59,16 @@ public PhErrorDTO validateTransactionRequest(Set<String> requiredHeaders, HttpSe
request.getHeader(HeaderConstants.PLATFORM_TENANT_ID), ChannelValidatorsEnum.INVALID_PLATFORM_TENANT_ID, 20,
ChannelValidatorsEnum.INVALID_PLATFORM_TENANT_ID_LENGTH);

// Checks for X-CallbackURL
if (request.getHeader(HeaderConstants.PLATFORM_TENANT_ID).equals(mtnTenant)) {
validatorBuilder.validateFieldIsNullAndMaxLengthWithFailureCode(resource, HeaderConstants.X_Callback_URL,
request.getHeader(HeaderConstants.X_Callback_URL), ChannelValidatorsEnum.INVALID_X_CALLBACK_URL, 100,
ChannelValidatorsEnum.INVALID_X_CALLBACK_URL_LENGTH);
} else {
validatorBuilder.validateFieldIgnoreNullAndMaxLengthWithFailureCode(resource, HeaderConstants.X_Callback_URL,
request.getHeader(HeaderConstants.X_Callback_URL), 100, ChannelValidatorsEnum.INVALID_X_CALLBACK_URL_LENGTH);
}

return handleValidationErrors(validatorBuilder);
}

Expand Down
4 changes: 4 additions & 0 deletions ph-ee-connector-channel/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ bpmn:
international-remittance-payee: "international_remittance_payee_process-{dfspid}"
international-remittance-payer: "international_remittance_payer_process-{dfspid}"
inboundTransactionReq-flow: "{ps}_flow_{ams}-{dfspid}"
mtn-transaction-request: "mtn_payee_transaction_request-{dfspid}"

mtn-transaction-request:
tenant: "rhino"

ams:
groups:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.mifos.connector.mockpaymentschema.api;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.mifos.connector.mockpaymentschema.schema.MtnRtpDTO;
import org.mifos.connector.mockpaymentschema.service.MtnCollectionService;
import org.mifos.connector.mockpaymentschema.service.SendCallbackService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Objects;

@Slf4j
@RestController
@RequestMapping("/mtn/collection")
public class MtnCollectionApi {

@Autowired
private MtnCollectionService mtnService;

@Autowired
private SendCallbackService callbackService;

@Value("${mtn.contactpoint}")
private String mtnContactpoint;

@Value("${mtn.endpoints.callback}")
private String callbackEndpoint;

@Autowired
private ObjectMapper objectMapper;


@PostMapping("/v1_0/requesttopay")
public <T> ResponseEntity<T> requestToPay(@RequestBody MtnRtpDTO request) throws JsonProcessingException {
ResponseEntity<T> response = mtnService.requestToPay(request);

String callbackUrl = mtnContactpoint + callbackEndpoint;
callbackService.sendCallback(objectMapper.writeValueAsString(response.getBody()), callbackUrl);


return response;
}

@GetMapping("/v1_0/requesttopay/{referenceId}")
public <T> ResponseEntity<T> getRequestToPayStatus(@PathVariable(name = "referenceId") String referenceId) throws
JsonProcessingException {
ResponseEntity<T> response = mtnService.getRequestToPayStatus(referenceId);

String callbackUrl = mtnContactpoint + callbackEndpoint;
callbackService.sendCallback(objectMapper.writeValueAsString(response.getBody()), callbackUrl);

return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.mifos.connector.mockpaymentschema.schema;

import lombok.*;

@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MtnRtpDTO {
private String amount;
private String currency;
private String externalId;
private Payer payer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.mifos.connector.mockpaymentschema.schema;

import lombok.*;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Payer {
private String partyIdType;
private String partyId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.mifos.connector.mockpaymentschema.schema;

import lombok.*;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RequestToPayFailureResponse {
private String externalId;
private String amount;
private String currency;
private Payer payer;
private String status;
private String reason;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.mifos.connector.mockpaymentschema.schema;

import lombok.*;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RequestToPaySuccessResponse {
private String financialTransactionId;
private String externalId;
private String amount;
private String currency;
private Payer payer;
private String status;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.mifos.connector.mockpaymentschema.service;


import org.mifos.connector.mockpaymentschema.schema.MtnRtpDTO;
import org.mifos.connector.mockpaymentschema.schema.Payer;
import org.mifos.connector.mockpaymentschema.schema.RequestToPayFailureResponse;
import org.mifos.connector.mockpaymentschema.schema.RequestToPaySuccessResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
public class MtnCollectionService {

@Value("${mtn.payerIdentifier.reject}")
private String rejectPayerId;

@Value("${mtn.payerIdentifier.delay}")
private String callbackDelayPayerId;

public <T> ResponseEntity<T> requestToPay(MtnRtpDTO requestBody) {
String partyId = requestBody.getPayer().getPartyIdType();
if (partyId.equals(rejectPayerId)) {
RequestToPayFailureResponse failureResponse = converterForFailureResponse(requestBody);
return ResponseEntity.status(HttpStatus.ACCEPTED).body((T) failureResponse);
}
RequestToPaySuccessResponse successResponse = converterForSuccessfulResponse(requestBody);
return ResponseEntity.status(HttpStatus.ACCEPTED).body((T) successResponse);
}

public <T> ResponseEntity<T> getRequestToPayStatus(String referenceId) {
Payer payer = Payer.builder()
.partyIdType("MSISDN")
.partyId(callbackDelayPayerId)
.build();

RequestToPaySuccessResponse response = RequestToPaySuccessResponse.builder().payer(payer).build();
return ResponseEntity.status(HttpStatus.ACCEPTED).body((T) response);
}


private RequestToPayFailureResponse converterForFailureResponse(MtnRtpDTO requestToPayDTO) {

return RequestToPayFailureResponse.builder()
.externalId(requestToPayDTO.getExternalId())
.amount(requestToPayDTO.getAmount())
.currency(requestToPayDTO.getCurrency())
.payer(requestToPayDTO.getPayer())
.status("FAILED")
.reason("APPROVAL_REJECTED")
.build();
}

private RequestToPaySuccessResponse converterForSuccessfulResponse(MtnRtpDTO requestToPayDTO) {

String financialTransactionId = UUID.randomUUID().toString().replace("-", "");
return RequestToPaySuccessResponse.builder()
.financialTransactionId(financialTransactionId)
.externalId(requestToPayDTO.getExternalId())
.amount(requestToPayDTO.getAmount())
.currency(requestToPayDTO.getCurrency())
.payer(requestToPayDTO.getPayer())
.status("SUCCESSFUL")
.build();
}
}
Loading