Ingénieur études et développement - progiciel monétique
NGUYEN Clément - INFO5
2023
> Entreprise : Sopra Banking Software
> Objectif : Développement de nouvelles fonctionnalités sur des progiciels monétique
> Participation au développement de RTSI (Real-Time Systems Inferface)
- Filiale du groupe Sopra Steria
- Spécialisée dans le secteur bancaire
- Agence "Cards" : département R&D monétique
- Monétique : Front and Back office
Contexte RTSI (Real-Time Systems Interface) :
Visa a crée un système en ligne nommée VROL
> permet d'échanger des documents liés à des litiges
Application RTSI
> permet de déclarer des litiges auprès de VROL
Temps de développement : 2 mois
Text
1. Organisation de l'équipe
2. Les technologies utilisées
Méthode Agile
- Agile Manager
- Scrum Master
- Product Owner
- Developers
1) Sprint planning
>> Product backlog
>> Grooming
2) Sprint
>> 2 semaines
3) Sprint Review
>> Démonstration
4) Rétrospective
>> Ce qu'on a aimé
>> Ce qu'on a appris
>> Ce qui a manqué
>> Les actions à faire
Architecture en microservices
> Spring boot : framework pour applications Java
- Automatise la configuration
- Automatise la mise en place d'applications
- Gestion de dépendances
> On gagne en productivité
Programmation réactive
> Approche de programmation pour gérer les flux de données et les événements.
> Réagit rapidement et de manière asynchrone aux changements.
Reactor
> Bibliothèque de programmation réactive.
> Gère les flux de données et les événements de manière asynchrone.
1. Processus de RTSI
2. Développement avec un scénario d'exemple
3. Les tests
Scénario d'exemple :
Appel n°1 : SubmitTranInquiry
> Récupérer informations de la transaction
Appel n°2 : InitiateDisputeFromTransaction
> Initialisation du dossier de litige
Si FraudRptInd = "NotExist"
> Exception
- FraudRptInd
Etape 1 : Configuration du JMS pour se connecter à ActiveMQ
@Configuration
@EnableJms
public class JmsConfiguration implements JmsListenerConfigurer {
private final ActiveMQProperties activeMqProperties;
public JmsConfiguration(ActiveMQProperties activeMqProperties) {
this.activeMqProperties = activeMqProperties;
}
@Bean
public ConnectionFactory activeMQConnectionFactory() {
ActiveMQConnectionFactory connectionFactory =
new ActiveMQConnectionFactory(activeMqProperties.credentials().userId(),
activeMqProperties.credentials().password(),
activeMqProperties.brokerUrl());
connectionFactory.setTrustedPackages(List.of(DXP_BASE_PACKAGE));
return connectionFactory;
}
}
> communication entre applications
Etape 2 : Configuration du clientDto
public record TranInquiryRequestClientDto(
@JsonProperty("RequestHeader")
RequestHeaderClientDto requestHeader,
@Valid
@JsonProperty("RequestData")
RequestDataClientDto requestData
) {}
public record RequestHeaderClientDto(
@JsonProperty("User")
UserClientDto user,
@JsonProperty("MemberRole")
MemberRole memberRole
) {}
public record RequestDataClientDto(
@JsonProperty("TransactionID")
String transactionID
) {}
> création de l'objet de la requête pour appeler /SubmitTranInquiry
user, memberRole et transactionID nécessaires pour appeler l'API
Etape 3 : Création du mapper
public static TranInquiryRequestClientDto toRequestClientDto(String input, CompanyCodesProperties properties) {
CompanyCodesProperties.CompanyCodeProperties property = getCompanyCodeFromProperties(input, properties);
return new TranInquiryRequestClientDto(
new RequestHeaderClientDto(new UserClientDto(property.userId(),
property.userType()),
toMemberRole(property)),
new RequestDataClientDto(getFieldValue("IdTransactionUniq", input))
);
}
> convertit des données d'un format en un autre
<message>
<entete>
<codReg>13001</codReg>
<codMessage>string</codMessage>
</entete>
<body>
<refIntOperation>string</refIntOperation>
<idTransactionUniq>1010000</idTransactionUniq>
<mntImpayeComp>4934200</mntImpayeComp>
<codDevMntBrutComp>840</codDevMntBrutComp>
</body>
</message>
transactionID = 1010000
Etape 4 : Création du consumer
> écouter et consommer les messages provenant de la queue
@JmsListener(destination = "${queues.queueChargebackRequest}")
public void processIssuerMessage(String message) {
Mono.just(message)
.doOnSubscribe(m -> logger.debug("Received message from Issuer: {}", message))
.filter(xmlMessageService::isMessageValid)
.flatMap(visaService::processVisaStages)
.flatMap(r -> sendResponse(toXml(r)))
.onErrorResume(Exception.class, e -> sendResponse(toXml(toResponse(message, emptyList(), e))))
.subscribe();
}
isMessageValid() : Vérification du message XML
processVisaStages() : Traitement du message
sendResponse() : Envoie d'un message retour dans une autre queue
Etape 5 : Traitement du message (scénario d'exemple)
public Mono<Response> processVisaStages(String issuerMsg) {
Map<VisaApi, Object> mapResponses = new EnumMap<>(VisaApi.class);
List<StepDone> steps = new ArrayList<>();
return Mono.just(mapResponses)
.flatMap(r -> checkIfChargebackIsNotAFraud(r, issuerMsg))
.flatMap(r -> processStageSubmitTranInquiry(issuerMsg, r, steps))
.flatMap(r -> processStageInitiateDisputeFromTransactionOrCase(issuerMsg, r, steps))
.flatMap(this::stopProcessWhenFraudReportNotExist)
.flatMap(r -> continueProcessIfFraudReportExist(issuerMsg, r, steps))
.map(r -> toResponse(issuerMsg, steps, null))
.onErrorResume(Exception.class, e -> Mono.just(toResponse(issuerMsg, steps, e)));
}
1
2
3
Etape 6 : Préparation de la requête et appel de l'API SubmitTranInquiry
private Mono<Map> processStageSubmitTranInquiry(String issuerMsg,
Map<VisaApi, Object> mapResponses,
List<StepDone> steps) {
return Mono.just(toRequestClientDto(issuerMsg, companyCodesProperties))
.map(validatorService::validate)
.flatMap(r -> submitTranInquiryService.submitTranInquiry(r, issuerMsg, steps))
.map(r -> putObjectInMap(mapResponses, SUBMIT_TRAN_INQUIRY, r));
}
public Mono<TranInquiryResponseClientDto> submitTranInquiry(
TranInquiryRequestClientDto request
) {
String requestId = String.valueOf(UUID.randomUUID());
return webClient.post()
.uri(API.getUri())
.bodyValue(request)
.retrieve()
.toEntity(TranInquiryResponseClientDto.class)
.doOnSubscribe(s -> logger.info(LOG_REQUEST, API.getUri(), requestId, toMaskedJson(request)))
.doOnNext(
r -> logger.info(LOG_RESPONSE, API.getUri(), requestId, r.getStatusCodeValue(), toMaskedJson(r.getBody())))
.map(HttpEntity::getBody);
}
URI = /submitTranInquiry
Etape 1 : Création des mocks
> Pas accès aux APIs de Visa
> Mockito : simuler les appels à Visa
Comment simuler l'appel de la première API ?
{
"request": {
"method": "POST",
"urlPathPattern": "/rsrv_rolti-mte2/api/submitTranInquiry",
"bodyPatterns": [
{
"matchesJsonPath": "$.RequestData[?(@.TransactionID == '1000000200')]"
}
]
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"Status": [
{
"Code": "I-300000000",
"Message": "Successfully completed Operation."
}
],
"ResponseData": {
"TIEventID": 27855353,
"TransactionSummary": [
{
"RolTransactionId": 172632355,
"TransactionType": "TC05"
}
]
}
}
}
}
Etape 2 : Tests unitaires
> Testons la fonction précédemment démontrée
@Test
void submitTranInquiry_shouldSucceed_whenStatus200() {
TranInquiryRequestClientDto request = datasetTranInquiryRequest("1000000200");
String issuerMessage = datasetXml("13001", "1000000200");
test(service.submitTranInquiry(request, issuerMessage, new ArrayList<>()))
.expectSubscription()
.consumeNextWith(r -> {
assertThat(r).isNotNull();
assertThat(r.status().get(0).code()).isEqualTo("I-300000000");
assertThat(r.responseData().tiEventID()).isEqualTo(27855353);
assertThat(r.responseData().transactionSummary().get(0).rolTransactionId()).isEqualTo(172632355);
})
.verifyComplete();
}
Idée de carrière
Amélioration de compétences techniques et transversales
Mon futur dans l'entreprise
Avez-vous des questions ?