Pular para o conteúdo

Desafios de entrevista em Go · parte 6 de 11

PicPay - Digital Wallet & Payment Gateway

avançadoPublicada em · 5 min de leitura

Tamanho da fonte:
Largura da coluna:

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:

  1. 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).
  2. 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.
  3. 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.
  4. 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, Senha e Saldo 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:

  1. Validar se o pagador não é um lojista.
  2. Validar se o pagador possui saldo suficiente para cobrir o valor da operação.
  3. 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.
  4. 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).
  5. 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 Forbidden ou 422 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

  1. Uso de context.Context com 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.
  2. 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.
  3. 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.
  4. Testes automatizados completos: Testes de tabela e testes de integração com banco de dados real em contêineres temporários via testcontainers-go.

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?