Skip to content

Challenge 15: Payment Initiation (PISP) — Initiating Payments via Open Finance

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


The PISP (or ISP - Payment Transaction Initiator) is the figure introduced by Phase 3 of Open Finance Brasil that allows third parties to initiate payments directly from the customer's bank account — without credit cards, boleto, or manual PIX. It's the foundation of "Pix Automático" and modern Direct Debit.

Switch: TypeScript vs Go

What is a PISP?

Use CaseExample
E-commerce checkoutPay with 1 click
Recurring subscriptionsNetflix, Spotify
Food deliveryiFood, Rappi
MobilityUber, 99
MarketplacesMercado Livre, Shopee

Complete Ecosystem

Detailed Flow

Payment APIs

EndpointDescription
POST /consentsSingle consent
POST /recurring-consentsFor recurrence
POST /pix/paymentsInitiate PIX
GET /pix/payments/{id}Check 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) {
    const existing = await this.idempotencyService.check(input.idempotencyKey);
    if (existing) return right(existing);

    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());

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

    const payment = PaymentInitiation.create({ ... });
    await this.paymentRepo.save(payment);

    const result = await this.fapiClient.initiatePayment(pispClientId, { ... });

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

    if (consent.type === 'SINGLE') consent.consume();
    await this.eventPublisher.publish('payment.initiated', { ... });

    return right(payment);
  }
}

Recurring Flow (Pix Automático)

Comparison: TypeScript vs Go

AspectTypeScriptGo
FAPI/JWTjose, jsonwebtokengolang-jwt/jwt
mTLSTLS nativenet/http mTLS
Performance~3K req/s~30K req/s
Memory~500MB~50MB
Latency P9930-100ms5-20ms
EcosystemRich (ready SDKs)Fewer FAPI libs

Real Cases

  • Mercado Pago (Go) — Largest PISP, 30K+ TPS, Pix Automático
  • PicPay (Go + TS) — 40M+ users, massive recurrence
  • iFood (Go) — Checkout at scale, multi-bank
  • Pluggy/Belvo (Go) — B2B PISP infrastructure

How to test

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

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

# Initiate payment
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":"Store","document":"12345678901","account":{"ispb":"12345678","number":"12345","accountType":"CACC"}}}'

Lessons learned

  1. PISP = Next frontier — After PIX and Open Finance
  2. Pix Automático — Recurrence without new approval
  3. FAPI mandatory — mTLS + PS256 + PKCE
  4. Idempotency critical — Payments never duplicated
  5. Webhooks with JWS — Signature validation
  6. State machine — CREATED → PDNG → ACSC (or RJCT)
  7. Daily reconciliation — With each account holder bank
  8. Go dominates at scale — 5-15x faster
  9. Payment consent ≠ Data consent — Different rules
  10. Smart routing — Choose best bank for each payment