Desafios de entrevista em Go · parte 6 de 11
PicPay - Digital Wallet & Payment Gateway
avançadoPublicada em · 5 min de leitura
Dados da empresa e da vaga
- Empresa: PicPay (PicPay Serviços S.A.)
- Sede: Vitória / São Paulo, Brasil (com atuação nacional e modelo remoto)
- Perfil da vaga: Software Engineer (Backend em Go / Microservices) — Níveis Pleno e Sênior
- Modelo de trabalho: Remoto
- Faixa salarial estimada da vaga:
- Pleno: R$ 14.000 a R$ 20.000/mês (CLT) / $40.000 a $55.000 USD/ano
- Sênior: R$ 20.000 a R$ 28.000/mês (CLT) / $60.000 a $85.000 USD/ano + Bônus anual e benefícios flexíveis
- Stack principal do time: Go, PostgreSQL, Apache Kafka, RabbitMQ, Docker, Kubernetes, AWS, Prometheus
Contexto do desafio técnico
O desafio PicPay Simplificado (Digital Wallet) é um dos testes técnicos práticos mais clássicos do mercado brasileiro de tecnologia para avaliação de habilidades em backend, microsserviços e resiliência de sistemas.
O objetivo é desenvolver uma API RESTful completa em Go para uma plataforma simplificada de pagamentos entre usuários comuns e lojistas, com validação de regras cadastrais, consulta a serviços autorizadores externos de terceiros, controle transacional estrito e envio de notificações assíncronas tolerantes a falhas.
O avaliador do PicPay analisa:
- Design de Microsserviços e Clean Architecture: Separação clara entre camada de transporte (HTTP Handlers), casos de uso (Domain/Use Cases) e infraestrutura externa (Repositories e Clientes HTTP).
- Resiliência e tolerância a falhas externas: Como o código lida com instabilidade, lentidão e erros 500 no serviço externo de autorização e no serviço de notificação.
- Consistência de dados (ACID): Transações de débito e crédito atômicas no banco com reversão total (rollback) caso a autorização seja negada.
- Concorrência e processamento em segundo plano: Notificação assíncrona do recebedor utilizando goroutines controladas, canais com buffer ou mensageria sem travar a resposta da requisição HTTP do pagador.
Regras de negócio e requisitos funcionais
1. Tipos de usuários e papéis de carteira
O sistema deve suportar dois tipos de carteiras com comportamentos distintos:
- Usuários Comuns (Pessoa Física): Podem transferir dinheiro para outros usuários comuns e para lojistas. Podem também receber transferências de qualquer usuário comum.
- Lojistas (Pessoa Jurídica): Podem apenas receber transferências. Lojistas não podem enviar dinheiro para ninguém.
2. Cadastro e validações de unicidade
- Cada carteira possui:
Nome Completo,CPF ou CNPJ,E-mail,SenhaeSaldo Inicial. - CPFs, CNPJs e e-mails devem ser estritamente únicos no banco de dados.
3. Fluxo transacional de transferência
Para que uma transferência seja concluída com sucesso:
- Validar se o pagador não é um lojista.
- Validar se o pagador possui saldo suficiente para cobrir o valor da operação.
- Consultar um serviço autorizador externo simulado (HTTP Mock). Se o serviço retornar negado ou estiver fora do ar com timeout, a transação deve ser abortada e nenhum saldo pode ser debitado.
- A transferência de fundos (débito no pagador e crédito no recebedor) deve ocorrer em uma única transação de banco de dados (
sql.Tx). - Se a transação for efetivada, disparar uma notificação assíncrona para o recebedor confirmando a entrada do valor. Se o serviço de notificação estiver instável, a transação não deve ser cancelada; em vez disso, a falha de notificação deve ser tratada em segundo plano com retentativas (retries).
Estrutura de dados e endpoints exigidos
1. Endpoint de Transferência
- Rota:
POST /transfer - Payload de entrada (JSON):
{ "value": 100.00, "payer": "4a7f058c-6878-4c12-8789-9a70e4c69b91", "payee": "8d3e210a-3142-4f99-8812-7b19a3c58e22" } - Respostas HTTP:
HTTP 200 OK: Transação autorizada e executada com sucesso.HTTP 400 Bad Request: Saldo insuficiente, pagador é lojista ou parâmetros inválidos.HTTP 403 Forbiddenou422 Unprocessable Entity: Autorização externa negada.HTTP 500 Internal Server Error: Erro interno no banco de dados com rollback garantido.
Arquitetura recomendada em Go
cmd/
api/
main.go # Inicialização de dependências e servidor HTTP
internal/
domain/
wallet.go # Entidades Wallet, UserType e Transaction
errors.go # Erros canônicos de domínio
usecase/
transfer.go # Regras de negócio, autorização e orquestração
repository/
postgres/
wallet_repository.go # Consultas SQL com 'FOR UPDATE' e transações
gateway/
authorizer.go # Cliente HTTP com timeout para o autorizador externo
notification.go # Cliente HTTP / Worker concorrente de notificações
Exemplo de implementação do caso de uso em Go:
package usecase
import (
"context"
"errors"
"fmt"
"time"
)
type AuthorizerClient interface {
Authorize(ctx context.Context, value float64) (bool, error)
}
type NotificationSender interface {
SendAsync(ctx context.Context, payeeEmail string, message string)
}
type WalletRepository interface {
ExecuteTransferTx(ctx context.Context, payerID, payeeID string, amountCents int64) error
IsMerchant(ctx context.Context, userID string) (bool, error)
}
type TransferUseCase struct {
repo WalletRepository
authorizer AuthorizerClient
notifier NotificationSender
}
func (u *TransferUseCase) Transfer(ctx context.Context, payerID, payeeID string, amount float64) error {
// 1. Validação de perfil (Lojistas não podem transferir)
isMerchant, err := u.repo.IsMerchant(ctx, payerID)
if err != nil {
return fmt.Errorf("erro ao verificar tipo de carteira: %w", err)
}
if isMerchant {
return errors.New("lojistas não estão autorizados a enviar transferências")
}
// 2. Consulta ao serviço autorizador externo com timeout estrito
authCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
authorized, err := u.authorizer.Authorize(authCtx, amount)
if err != nil || !authorized {
return errors.New("transação não autorizada pelo serviço externo")
}
// 3. Execução transacional no banco de dados (ACID)
amountCents := int64(amount * 100)
if err := u.repo.ExecuteTransferTx(ctx, payerID, payeeID, amountCents); err != nil {
return fmt.Errorf("falha ao debitar e creditar carteiras: %w", err)
}
// 4. Disparo de notificação assíncrona (não bloqueia a resposta da API)
u.notifier.SendAsync(context.Background(), payeeID, fmt.Sprintf("Você recebeu uma transferência de R$ %.2f", amount))
return nil
}
O que os avaliadores do PicPay analisam
- Uso de
context.Contextcom timeout: As chamadas para os serviços externos de autorização e notificação devem obrigatoriamente usar timeouts curtos (ex.: 2 a 3 segundos) para evitar que a indisponibilidade de um parceiro esgote todas as goroutines do servidor Go. - Desacoplamento por interfaces: O caso de uso não deve depender diretamente de structs concretas do banco de dados ou de bibliotecas HTTP, mas de interfaces (
AuthorizerClient,WalletRepository), facilitando testes unitários com mocks. - Resiliência na notificação: Se o serviço de notificação falhar, a transferência não pode sofrer rollback, pois o dinheiro já mudou de dono com sucesso. Avaliadores esperam que a notificação seja enfileirada em segundo plano ou gravada para reprocessamento.
- Testes automatizados completos: Testes de tabela e testes de integração com banco de dados real em contêineres temporários via
testcontainers-go.