Skip to main content

Entendendo a API do beneficio

Para o exemplo vamos simular um beneficio chamado “Dummy Saúde”, a Dummy Saúde possui uma API com os seguintes recursos:
  • POST /customer/ - Cria um novo cliente
  • POST /customer/{:id}/plan - Adiciona um novo cliente no plano de saude
  • GET /customer/:cpf - Recupera os dados de um cliente
  • DELETE /customer/{:id}/plan - Remove um cliente do plano de saude
  • DELETE /customer/{:id} - Deleta um cliente

Criando o projeto

Requerimentos Node 22 Instalação npm i -g @benup/bensdk Criando o projeto ben init teste-app Será gerado uma pasta chamada teste-app Abra a pasta com o edito de código de sua preferencia

Definindo o beneficio

Abra o arquivo benefit-definition.ts dentro da pasta src, esse arquivo é responsavel por informar ao Integrador como seu benAPP será executado, vamos entender ele abaixo. O benefitID é o identificador único do seu beneficio, você receberá o seu quando iniciar a integração
const benefitDefinition = {
	benefitID: 'TEST-APP',
	...
}
O availableActions define quais ações o benAPP manipula, podendo ser GRANT, REVOKE, RECHARGE, como o nosso beneficio “Dummy Saúde” é um plano de saúde apenas o GRANT e o REVOKE já é o suficiente.
const benefitDefinition = {
	benefitID: 'TEST-APP',
	availableActions: ['GRANT', 'REVOKE'],
	...
}

Configurando a máquina de estado

Um benAPP é composto por uma sequencia de funções chamada handlers que são executadas em uma ordem pré definida. Essa ordem é definida dentro do benefit-definition.ts em um campo chamado stateMachine e é o ponto mais importante da nossa integração. O projeto já gerou ela pra gente como está abaixo, vamos adaptar para nosso cenário:
stateMachine: {
	GRANT: {
		REQUESTED_GRANT: {
			next: 'SYNC_EXISTING_GRANT'},
		SYNC_EXISTING_GRANT: {
			next: 'REQUEST_CREATE_EMPLOYEE',
			skip: 'GRANTED'},
		REQUEST_CREATE_EMPLOYEE: {
			next: 'GRANTED',
			fail: 'FAILED_TO_GRANT'}},
  
	REVOKE: {
		REQUESTED_REVOKE: {
			next: 'SYNC_EXISTING_REVOKE'},	
		SYNC_EXISTING_REVOKE: {
			next: 'REQUEST_REMOVE_EMPLOYEE',
			skip: 'REVOKED'},
			
		REQUEST_REMOVE_EMPLOYEE: {
			next: 'REVOKED',
			fail: 'FAILED_TO_REVOKE'}}
},

Fluxo de GRANT

Conforme a API do nosso “Dummy Saúde” para realizar um GRANT precisamos:
  • Verificar se o cliente existe realizando um GET /customer/:cpf
  • Caso o cliente exista encerra o fluxo.
  • Caso o cliente não exista,
    • Criamos um novo cliente em POST /customer/
    • Adicionamos o cliente no plano em POST /customer/:id/plan
    • Encerramos o fluxo
Na nossa maquina de estado esse fluxo é configurado da seguinte forma:
stateMachine: {
	GRANT: {
		REQUESTED_GRANT: {
			next: 'SYNC_EXISTING_GRANT'},
		SYNC_EXISTING_GRANT: {
			next: 'REQUEST_CREATE_EMPLOYEE',
			skip: 'GRANTED'},
		REQUEST_CREATE_EMPLOYEE: {
			next: 'REQUEST_ADD_PLAN',
			fail: 'FAILED_TO_GRANT'}
		REQUEST_ADD_PLAN: {
			next: 'GRANTED',
			fail: 'FAILED_TO_GRANT'}},
	...
},

Fluxo de REVOKE

Para REVOKE temos o seguinte fluxo:
  • Verificar se o cliente existe realizando um GET /customer/:cpf
  • Caso não exista, encerra o fluxo.
  • Caso o cliente exista:
    • Removemos o funcionario do plano em: DELETE /customer/{:id}/plan
    • Desativamos o cliente em: DELETE /customer/{:id}
    • Encerramos o fluxo
Na nossa maquina de estado esse fluxo é definido da seguinte forma:
stateMachine: {
	REVOKE: {
		REQUESTED_REVOKE: {
			next: 'SYNC_EXISTING_REVOKE'},
		SYNC_EXISTING_REVOKE: {
			next: 'REQUEST_DEACTIVATE_PLAN',
			skip: 'REVOKED'},
		REQUEST_DEACTIVATE_PLAN: {
			next: 'REQUEST_REMOVE_EMPLOYEE',
			skip: 'REVOKED'},
		},
		REQUEST_REMOVE_EMPLOYEE: {
			next: 'REVOKED',
			fail: 'FAILED_TO_REVOKE'}}
	...
},
Importante Para cada requisição na API do beneficio, deve ser criado um novo estado

Desenvolvendo seu benApp

Declarando logs

Para cada estado que criamos, vamos criar um esquema de logs, os logs são importantes para auditoria, investigação de problemas e histórico.
log: {
  GRANT: z.object({
    SYNC_EXISTING_GRANT: z.object({}),
    REQUEST_CREATE_EMPLOYEE: z.object({}),
    REQUEST_ADD_PLAN: z.object({}),
  }),

  REVOKE: z.object({
    SYNC_EXISTING_REVOKE: z.object({}),
    REQUEST_REMOVE_EMPLOYEE: z.object({}),
    REQUEST_DEACTIVATE_PLAN: z.object({})
  })
}

Gerando seus handlers

Agora, iremos criar uma pasta chamada handlers dentro do src e para cada estado na maquina que não seja os iniciais e os finais iremos gerar um arquivo, Porem não precisamos fazer isso manualmente, basta executar npm run generate na pasta raiz do nosso test-app, se o nosso benefit-definition.ts estiver configurado configurado corretamente, os arquivos serão gerados.

Primeira integração

Configurando o contexto

A primeira coisa que vamos fazer é mockar a API do nosso “Dummy Saúde”, para isso vamos abrir o arquivo context.config.ts e colocar o seguinte trecho após a declaração do apiMocks.ts.
apisMocks.benefitsAPI.onPost('/customer/').replyOnce(201, {
  id: '1',
  name: 'João da Silva',
  cpf: '41786828650'
});

// Adiciona um novo cliente no plano de saúde
apisMocks.benefitsAPI.onPost('/customer/1/plan').replyOnce(200, {
  message: 'Cliente adicionado ao plano com sucesso'
});

// Recupera os dados de um cliente pelo CPF
apisMocks.benefitsAPI.onGet('/customer/41786828650').replyOnce(200, {
  id: '1',
  name: 'João da Silva',
  cpf: '41786828650',
  planActive: true
});

// Remove um cliente do plano de saúde
apisMocks.benefitsAPI.onDelete('/customer/1/plan').replyOnce(200, {
  message: 'Cliente removido do plano com sucesso'
});

// Deleta um cliente
apisMocks.benefitsAPI.onDelete('/customer/1').replyOnce(200, {
  message: 'Cliente deletado com sucesso'
});
Importante Caso você queria rodar em um ambiente de staging ou homologação, pode fazer isso mudando a benefitsAPI para uma instancia real do Axios. O context.config.ts é completamente ignorado pelo Integrador, ele é nosso Playground, o Integrador irá preencher ele com informações reais
E onde está a autenticação?
A autenticação é feita pelo integrador e não precisamos nos preocupar com isso dentro de um benApp, essa etapa será feita durante a revisão do seu beneficio.

Modificando o handler

Vamos começar a alterar nosso handler sync_existing_grant.handler.ts ! Vamos buscar o funcionario na API, perceba que a API do nosso beneficio “Dummy Saúde” já existe no contexto, pois o Integrador ja fez isso.
ctx.logger.info('Buscando o funcionário');

const res = await ctx.benefitsAPI.get<BenefitResponse>(`/customer/${action.target.employee.cpf}`);

const { status, data } = res;
Em caso de erro durante a API, vamos finalizar a execução do handler dizendo para o Integrador o que ele deve fazer em seguida:
if (status == 500) {
  ctx.logger.info('Erro ao buscar o funcionário');
  
  return {
    handlerResponse: {
      response: "UNACK",
      options:{
        maxRetries: 5, // Diz para o integrador re tentar no máximo 5 vezes
        delayBetweenRetries: 5, //Delay em segundos entre as tentativas
        // Pode ser "fixed" ou "exponential" 
        // fixed: Retenta a execuçãp a cada 5 segundos 
        // exponential: Retenta a execução em um tempo exponencial, por exemplo
        // Primeira tentativa: 5 segundos depois
        // Segunda tentativa: 25 segundos depois
        // Terceira tentativa: 625 segundos depois
        delayMode: "fixed", 
      }
    },
  };
}
Caso a resposta seja executada com sucesso o handler irá dizer qual o proximo handler deve ser executado:
return {
  messageResponse: 'ACK',
  action: {
    state: data != null ? stateMachine.skip : stateMachine.next,
    ctx: {},
    logs: {
      benefitAPI: getLogsFromResponse(res)
    }
  }
};

Trabalhando com contexto

O contexto são variáveis que são passadas entre a execução dos handlers. As variáveis do contexto são declaradas no benefit-definition.ts dentro do actions vamos modificar o campo ctx adicionando o funcionarioId
{
  actions: {
    ctx: z.object({
      funcionarioId: z.string().optional()
    }),
  }
}
Vamos preencher essa variavel na execução do request_create_employee.handler.ts
ctx.logger.info('Criando o funcionário');
const res = await ctx.benefitsAPI.post<BenefitResponse>(`/customer/`, {
  data:{
    cpf: action.target.employee.cpf
  }
});

const { status, data } = res;
//////......
return {
  handlerResponse: {
    response: "ACK"
  },
  action: {
    state: stateMachine.next,
    ctx: {
      funcionarioId: data.id //Preenchendo o contexto
    },
    logs: {
      benefitAPI: getLogsFromResponse(res)
    }
  }
};
Para a próxima execução, no arquivo request_add_plan.handler.ts vamos precisar saber qual o ID do funcionario, como ele ja foi preenchido no step anterior, vamos apenas recuperar ele:
const funcionarioID = action.ctx.funcionarioId;
ctx.logger.info('Adicionando o beneficio');
const res = await ctx.benefitsAPI.post<BenefitResponse>(`/customer/${funcionarioID}/plan`, );

const { status, data } = res;
///TRATAMENTO DE ERRROS 

//RETORNA A RESPOSTA
return {
  handlerResponse: {
    response: "ACK"
  },
  action: {
    state: stateMachine.next,
    ctx: {},
    logs: {
      benefitAPI: getLogsFromResponse(res)
    }
  }
};

Testando

O projeto do benSDK ja contem uma ferramenta de desenvolvedor integrada. Para executar basta rodar o comando npm start. Na seção State machine, selecione GRANT e em seguida clique em run.