Skip to content

Desafio 15: Payment Initiation (PISP) — Iniciando Pagamentos por Open Finance

🇧🇷 Iniciador de Transação de Pagamento
🇬🇧 Payment Initiation Service Provider


O PISP (ou ISP) é a figura introduzida pela Fase 3 do Open Finance Brasil que permite que terceiros iniciem pagamentos diretamente da conta bancária do cliente — sem cartão, sem boleto, sem PIX manual. É a base do Pix Automático e Pagamento por Débito em Conta moderno.

Switch: TypeScript vs Go

O que é um PISP?

Caso de UsoExemplo
Checkout e-commercePagar com 1 clique
Assinaturas recorrentesNetflix, Spotify
Food deliveryiFood, Rappi
MobilidadeUber, 99
MarketplacesMercado Livre, Shopee

Ecossistema Completo

Fluxo Detalhado

APIs de Pagamento

EndpointDescrição
POST /consentsConsentimento único
POST /recurring-consentsPara recorrência
POST /pix/paymentsInicia PIX
GET /pix/payments/{id}Consulta status
POST /automatic-paymentsPix Automático

Domain — Payment Entity

typescript
export enum PaymentStatus {
  CREATED = 'CREATED',
  PENDING = 'PDNG',
  ACCEPTED_CREDIT = 'ACSC',
  REJECTED = 'RJCT',
  CANCELLED = 'CANC',
}

export class PaymentInitiation extends Entity<string> {
  public confirm(endToEndId: string): void {
    this.props.status = PaymentStatus.ACCEPTED_CREDIT;
    this.props.endToEndId = endToEndId;
    this.props.confirmedAt = new Date();
  }

  public reject(reason: string): void {
    this.props.status = PaymentStatus.REJECTED;
    this.props.rejectionReason = reason;
  }

  public canBeProcessed(): boolean {
    return !this.isConfirmed() && !this.isRejected()
      && new Date() <= this.props.expiresAt;
  }
}
typescript
export class PaymentConsent {
  public canBeUsed(): boolean {
    return this.isAuthorised()
      && new Date() <= this.props.expirationDateTime;
  }

  public validatePayment(amount: number): boolean {
    if (!this.canBeUsed()) return false;
    if (this.props.type === PaymentConsentType.SINGLE) {
      return this.props.amount === amount;
    }
    if (this.props.recurringPolicy?.maxAmountPerTransaction) {
      return amount <= this.props.recurringPolicy.maxAmountPerTransaction;
    }
    return true;
  }

  public consume(): void {
    if (this.props.type === PaymentConsentType.SINGLE) {
      this.props.status = PaymentConsentStatus.CONSUMED;
    }
  }
}

Payment Initiator Service

typescript
export class PaymentInitiatorService {
  public async initiate(input: InitiatePaymentInput, pispClientId: string) {
    // 1. Idempotência
    const existing = await this.idempotencyService.check(input.idempotencyKey);
    if (existing) return right(existing);

    // 2. Valida consentimento
    const consent = await this.fapiClient.getPaymentConsent(pispClientId, input.consentId);
    if (!consent.canBeUsed()) return left(new ConsentNotActiveError());
    if (!consent.validatePayment(input.amount)) return left(new ExceedsLimitError());

    // 3. Fraud check
    const fraudCheck = await this.fraudService.preInitiationCheck({ ... });
    if (fraudCheck.isHighRisk) return left(new FraudDetectedError());

    // 4. Cria pagamento
    const payment = PaymentInitiation.create({ ... });
    await this.paymentRepo.save(payment);

    // 5. Envia ao banco via FAPI
    const result = await this.fapiClient.initiatePayment(pispClientId, { ... });

    if (result.value.status === 'ACSC') payment.confirm(result.value.endToEndId);
    else payment.reject(result.value.reason);

    // 6. Consome consent (single)
    if (consent.type === 'SINGLE') consent.consume();

    // 7. Publica evento
    await this.eventPublisher.publish('payment.initiated', { ... });

    return right(payment);
  }
}

FAPI Client — Comunicação com Bancos

typescript
export class FAPIClient {
  public async initiatePayment(clientId: string, payment: FAPIPaymentRequest) {
    const bankEndpoint = await this.directoryService.findConsentEndpoint(payment.consentId);
    const token = await this.getAccessToken(clientId, payment.consentId);

    // Monta payload e assina com JWS (PS256)
    const signedRequest = await this.signRequest({
      data: { consentId: payment.consentId, amount: payment.amount, ... }
    }, clientId);

    const response = await this.tlsClient.post(
      `${bankEndpoint}/open-banking/payments/v1/payments`,
      { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/jwt' }, body: signedRequest }
    );

    return this.mapPaymentResponse(await response.json());
  }

  private generateClientAssertion(clientId: string, audience: string): string {
    return sign({ iss: clientId, sub: clientId, aud: `${audience}/token`, jti: uuidv4(), exp: now + 60 },
      this.privateKey, { algorithm: 'PS256', header: { kid: this.keyId, typ: 'JWT' } });
  }
}

Fluxo de Recorrência (Pix Automático)

Comparação: TypeScript vs Go

AspectoTypeScriptGo
FAPI/JWTjose, jsonwebtokengolang-jwt/jwt
mTLSTLS nativonet/http mTLS
Performance~3K req/s~30K req/s
Memory~500MB~50MB
Latência P9930-100ms5-20ms
EcossistemaRico (SDKs prontos)Menos libs FAPI

Casos Reais

  • Mercado Pago (Go) — Maior PISP, 30K+ TPS, Pix Automático
  • PicPay (Go + TS) — 40M+ usuários, recorrência massiva
  • iFood (Go) — Checkout em escala, multi-bank
  • Pluggy/Belvo (Go) — Infraestrutura PISP B2B

Como testar

bash
# TypeScript
pnpm --filter @banking/pisp dev

# Go
cd packages/backend/pisp-go
go run .

# Iniciar pagamento
curl -X POST http://localhost:3007/api/v1/payments/initiate \
  -H "Content-Type: application/json" \
  -H "x-pisp-client-id: fintech-abc" \
  -d '{"consentId":"uuid","idempotencyKey":"uuid","amount":5000,"creditor":{"name":"Loja","document":"12345678901","account":{"ispb":"12345678","number":"12345","accountType":"CACC"}}}'

Lições aprendidas

  1. PISP = Próxima fronteira — Após PIX e Open Finance
  2. Pix Automático — Recorrência sem nova aprovação
  3. FAPI obrigatório — mTLS + PS256 + PKCE
  4. Idempotência crítica — Pagamentos nunca duplicados
  5. Webhooks com JWS — Validação de assinatura
  6. State machine — CREATED → PDNG → ACSC (ou RJCT)
  7. Reconciliação diária — Com cada banco detentor
  8. Go domina alta escala — 5-15x mais rápido
  9. Consent de pagamento ≠ Consent de dados — Regras diferentes
  10. Smart routing — Escolhe melhor banco para cada pagamento