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 Case | Example |
|---|---|
| E-commerce checkout | Pay with 1 click |
| Recurring subscriptions | Netflix, Spotify |
| Food delivery | iFood, Rappi |
| Mobility | Uber, 99 |
| Marketplaces | Mercado Livre, Shopee |
Complete Ecosystem
Detailed Flow
Payment APIs
| Endpoint | Description |
|---|---|
POST /consents | Single consent |
POST /recurring-consents | For recurrence |
POST /pix/payments | Initiate PIX |
GET /pix/payments/{id} | Check status |
POST /automatic-payments | Pix 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;
}
}Payment Consent
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
| Aspect | TypeScript | Go |
|---|---|---|
| FAPI/JWT | jose, jsonwebtoken | golang-jwt/jwt |
| mTLS | TLS native | net/http mTLS |
| Performance | ~3K req/s | ~30K req/s |
| Memory | ~500MB | ~50MB |
| Latency P99 | 30-100ms | 5-20ms |
| Ecosystem | Rich (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
- PISP = Next frontier — After PIX and Open Finance
- Pix Automático — Recurrence without new approval
- FAPI mandatory — mTLS + PS256 + PKCE
- Idempotency critical — Payments never duplicated
- Webhooks with JWS — Signature validation
- State machine — CREATED → PDNG → ACSC (or RJCT)
- Daily reconciliation — With each account holder bank
- Go dominates at scale — 5-15x faster
- Payment consent ≠ Data consent — Different rules
- Smart routing — Choose best bank for each payment