Skip to content

skywalkerluc/payment-service-provider-api

 
 

Repository files navigation

Payment Service Provider (PSP)

Uma versão super simplificada de um Payment Service Provider (PSP) como o Pagar.me (e inspirado nele). Um overview de como funciona pagamentos no Brasil.

Summary

Features

Estrutura Multilayer folder
Foi implementado Clean Architecture com uma estrutura robusta camada por camada que desacopla o núcleo da lógica de negócio dos frameworks.
Pronto para produção
Setup com PM2 em modo cluster (tema de uma palestra minha na NodeBR) e com as variavéis necessárias para o app está pronto para ser escalado. Para, por exemplo, um projeto simples já estaria mais que suficiente. Mas no mundo real, os problemas com escalabilidade são reais (Black Friday que o diga). Assim, o sistema também foi provisionado na AWS (tema de uma palestra minha no TDC), daí testei três cenários:
  • EC2 com docker-compose --> escalabilidade vertical;

  • AWS Lambda com Serverless --> escalabilidade horizontal

  • K8s com EKS --> escalabilidade tanto vertical como horizontal (não que não dê para escalar EC2 e serverless assim, mas não é o foco). Local foi testado com Minikube.

ORM para integração com o banco de dados
Como ORM foi usado o Sequelize. Foi ponderado o Slonik também, mas no final, não o escolhi. Para as queries mais complexas, como as que envolviam transaction, não foi usado ORM, para ganhar performance! Pois foi visto que em algumas queries Sequelize era redundante, as que não eram, foi usado ORM.
Preparado para testes
Foi usado jest como framework para realizar essa tarefa. Tanto para os unitários como para os de integração e end to end. Até a feature de capture (na qual um cliente pode pedir autorização mas não capturar naquele momento, pegando o id da transação e capturando depois) os testes estavam com 100% de cobertura. Mas depois dessa feature, que foi gigante, os testes caíram por volta dos 90% (mais na frente falarei como rodar os testes e como gerar cobertura).
Injeção de depêndencia
Existem várias lib que proveem suporte para injeção de depêndencia (eu particulamente gosto da awilix). Mas como o projeto é relativamente simples, e para melhorar a compreensão, não usei nenhuma. Esse conceito facilita bastante os testes. Também vale salientar que a camada "Domain" é 100% agnóstica a tecnologia, todos requires dessa camada não podem envolver tecnologia.
Schedule Cron Jobs para enviar e-mail
Foi programado para de meia noite (configurado via variável de ambiente) enviar e-mail para todos usuários ativos com o saldo atualizado.
Logger
Inicialmente estava pensando em fazer o sistema de logger com a ELK Stack. Não fiz, mas mantive a arquitetura, pra integrar com algum sistema de logger só precisa passar um callback (atualmente estou passando o callback console.log mesmo, só para efeitos de testes).

Quando o request passa pelo middleware inicial de logger, é gerado um uuid e esse mesmo uuid se permeia para toda a cadeia de middleware. Assim, dando algum bug o cliente pode pegar esse uuid, fazer uma busca na interface do sistema de log e fazer o trace do request dentro do projeto para entender o que aconteceu.

CLI integration
Sequelize é uma mão na roda nesse contexto. migrações e seeders são feitos quase que sem nenhum overhead. Falarei sobre como usar-los mais na frente.
Linter
Para code styling foi usado eslint.

Princípios básicos

A API é RESTful, e todas suas respostas são em JSON, no endpoint base:

http://<ip>:3000/1/

A seguir, algumas convenções da API:

Paginação

Há muitas rotas de listagem de entidades na API. Em todas elas é necessário lidar com um sistema de paginação para percorrer todas as instâncias. Esse sistema refere-se aos parâmetros count e page. Count representa quantos resultados por página deverão ser retornados — se não for informado um valor, o padrão é 10, e seu limite é 1000. Page é a página a ser retornada e se não for informado um valor, o padrão é 1.

Autenticação

Deve passar como forma de autenticação a API Key, chave que pode ser encontrada quando realizar o login ou criar uma conta. A API Key pode ser informada de quatro forma diferentes:
1 - No corpo da requisição como valor do parâmetro api_key
2 - Na url (query param) como valor do parâmetro api_key
3 - No header como valor do parâmetro api_key
4 - Basic Auth com username igual à chave e senha igual a api_key

Versionamento

Da api: deve ser passado no header, com key X-PagarMe-Version e value [v1, ...].
De infra: deve ser passado no primeiro argumento do endpoint. O padrão é 1.

Ambientes de teste e produção

Para transacionar você tem acesso a duas Chaves de API distintas, uma para teste e outra para produção. Dessa forma, o endpoint é o mesmo, sendo possível diferenciar o ambiente apenas escolhendo a chave apropriada para o tipo de operação que você deseja fazer.

Arquitetura

Banco de dados

Foi usado Posgres, com duas instâncias para produção e duas instâncias para testes locais (no código chamei-as de jest, para não confundir com a instância de testes em produção). Existia também a possibilidade de usar um banco só, colocando as 4 chaves (ak_test, ak_prod, ek_test, ek_prod) numa mesma tabela. Mas por questões de performances, escolhi a primeira, com bancos de dados separados.

Middlewares

Existem vários middlewares para de fato chegar no controlador do endpoint. Vou explicar os principais, em ordem de acesso:

Middlewares Responsabilidade
responseHandler Faz um wraper dos tipos de erros
setInfraVersion Seta a versão da infraestrutura. é o primeiro parâmetro da url. o default é 1. ex: url/1/transaction
setApiVersion Seta a versão da api que é passada no header com key X-PagarMe-Version
setKeyAndEnvironment Seta a chave que o usuário usou e de acordo com a chave se é o ambiente de testes ou de prod.
auth Captura a chave do usuário do middleware anterior e faz um find no banco para descobrir se existe tal chave e para setar o id do usuário. Note que também poderia ser usada a key do usuário nas queries, mas usando o id performa mais.

Configurando o ambiente e subindo a aplicação

O .env foi commitado para facilitar os testes.
Navegue até a raiz do projeto.

Com docker-compose

$ docker-compose -f docker-compose.yml build && docker-compose -f docker-compose.yml up -d

Obs: se rodar compose stop, e depois compose up para subir novamente, irá dar erro pois os bancos já estarão criados.
Daí precisa-se rodar compose down seguido de compose up:

$ docker-compose -f docker-compose.yml down && docker-compose -f docker-compose.yml build && docker-compose -f docker-compose.yml up -d

Se ainda assim tiver problemas, sugiro subir uma instância RDS e setar as variáveis no .env

Com docker, sem docker-compose

Renomeie o ".env.nodockercompose" para ".env" e roda:

$ docker run -p 35432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=docker -d postgres && npm install && npm run db:init && npm start

Sem docker

Precisa setar as variáveis de ambiente do banco no .env e rodar os seguintes comandos:
Para criar/migrar/povoar o banco:

$ npm install && npm run db:init

Para subir a aplicação:

$ npm install && npm start

Testando via Swagger

O acesso é através do endpoint htt://:3000/1/swagger

O primeiro passo é cadastrar um usuário para ter acesso a api_key. Em seguida, para transacionar entre os endpoints que não são de usuário, é preciso setar a api_key no Authorize do swagger.

Para exemplificar, segue o fluxo de criar uma transação com: capture = false, paymentMethod = debit_card e usuário tendo fundos no cartão (no mocker setei que cartões master não tem fundos para transações >= 50000, visa e o resto não tem para >= 100000):

criando usuário e copiando ak_test_:
criando usuário

setando ak_test_ no swagger:
criando usuário

criando transação e copiando o id
criando usuário

Fazendo uma pesquisa pelo id da transação (note que ainda não tem payable pois capture = false):
criando usuário

Fazendo um capture (agora, se novamente fizer uma pesquisa pelo id da transação, terá um payable)
criando usuário

Balance para status = paid
criando usuário

Balance para status = waiting_funds
criando usuário

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 98.0%
  • Shell 1.9%
  • Dockerfile 0.1%