Skip to content

Repository files navigation

Nexa API

Core banking / e-wallet backend for Nexa — a digital social media and e-wallet platform. Built with NestJS, PostgreSQL (via Prisma), and Redis.

MVP scope. This scaffold implements the minimum viable set of endpoints needed to register a user, verify identity (KYC), hold a wallet balance, and move money (cash-in, cash-out, P2P transfer) — with the security and compliance scaffolding a regulated e-money issuer in the Philippines needs in place from day one. It is a strong foundation, not a finished, audited banking system — see "Before going live" below.

Stack

  • Framework: NestJS 10 (Express platform)
  • Database: PostgreSQL via Prisma ORM
  • Cache/Sessions: Redis
  • Auth: JWT (short-lived access + rotating refresh tokens), Argon2id password hashing, optional TOTP MFA
  • Validation: class-validator / class-transformer, whitelist-strict DTOs
  • Docs: Swagger/OpenAPI (auto-disabled in production)

Project structure

src/
  auth/              registration, login, refresh/rotation, MFA
  users/             profile, KYC submission & history
  wallets/           balance, ledger/statement
  transactions/      cash-in, cash-out, P2P transfer (core money-movement engine)
  virtual-accounts/  partner-bank virtual account issuance + inbound credit webhook
  instapay/          InstaPay participating-bank directory + instant interbank transfer
  loans/             loan products, application/auto-decisioning, amortized repayment
  audit/             append-only compliance audit trail
  health/            liveness/readiness probes
  common/            guards, filters, interceptors, decorators, encryption service
  config/            typed configuration + env validation
  prisma/            Prisma service/module
  stellar/           Stellar wallet provisioning and asset-trustline support
prisma/
  schema.prisma      data model
  seed.ts            seeds tier limits, InstaPay bank directory, starter loan products

Getting started

cp .env.example .env
npm install
npm run prisma:migrate
npm run seed
npm run start:dev

Stellar environment variables

Add these values to your .env before running the API:

STELLAR_NETWORK=testnet
STELLAR_HORIZON_URL="https://horizon-testnet.stellar.org"
STELLAR_DISTRIBUTOR_SECRET="replace-with-your-distributor-secret"
STELLAR_STARTING_BALANCE_XLM=5
STELLAR_AUTO_FUND=true
STELLAR_ISSUER_PHP_PUBLIC="replace-with-php-issuer-public-key"
STELLAR_ISSUER_USD_PUBLIC="replace-with-usd-issuer-public-key"
STELLAR_ISSUER_EUR_PUBLIC="replace-with-eur-issuer-public-key"
STELLAR_ISSUER_INR_PUBLIC="replace-with-inr-issuer-public-key"
STELLAR_ISSUER_GBP_PUBLIC="replace-with-gbp-issuer-public-key"
STELLAR_ISSUER_JPY_PUBLIC="replace-with-jpy-issuer-public-key"

When STELLAR_AUTO_FUND=false or STELLAR_STARTING_BALANCE_XLM=0, the API will provision a Stellar keypair for the wallet without creating the on-chain account. The user must deposit XLM to activate the wallet.

Netbank / InstaPay environment variables

NETBANK_CLIENT_ID="replace-with-your-netbank-client-id"
NETBANK_CLIENT_SECRET="replace-with-your-netbank-client-secret"
PARTNER_WEBHOOK_SECRET="replace-with-a-long-random-secret-used-to-verify-inbound-webhooks"
NETBANK_VCA_PARTNER_ALIAS="12345"
INSTAPAY_CEILING_PHP=50000

PARTNER_WEBHOOK_SECRET is used to authenticate inbound virtual-account credit notifications from the partner banking integration.

InstaPay QR support

The API exposes new InstaPay QR endpoints:

  • POST /instapay/qr — issue a payment-ready InstaPay QR payload via Netbank.
  • POST /instapay/qr/decode — decode/validate a scanned InstaPay QR payload via Netbank.

See docs/instapay-qr.md for request/response examples.

Feature scope

Beyond the core wallet/transfer engine, this API implements three additional capabilities modeled on the embedded-banking (BaaS) feature set that Philippine BaaS providers like NetBank.ph publicly describe offering to their fintech partners — account issuance, payments, and loan support, all via API. This is inspiration for scope, not a clone of their actual implementation: NetBank.ph's real API contracts, partner integrations, and underwriting logic are proprietary and not publicly documented, so nothing here reproduces their actual system — it's an independent implementation built to cover the same category of features for Nexa's own wallet.

  • Virtual accounts (/virtual-accounts) — issue a partner-bank virtual account number mapped 1:1 to a user's wallet, so any ordinary bank transfer into that number becomes a wallet cash-in. Includes an HMAC-signed inbound webhook (POST /virtual-accounts/webhook/credit) for the partner bank to notify us when funds land — idempotent on the partner's own reference so retried webhook deliveries never double-credit the wallet.
  • InstaPay transfers (/instapay) — send funds instantly to any InstaPay-participating bank or e-wallet. Validates the destination against a participating-bank directory and enforces InstaPay's own per-transaction ceiling (configurable, see INSTAPAY_CEILING_PHP) in addition to the account's KYC-tier limits.
  • InstaPay QR support — new POST /instapay/qr and POST /instapay/qr/decode endpoints let the API issue payment-ready QR payloads and decode/validate scanned payloads through Netbank.
  • Stellar wallet assets — new user registrations now provision a Stellar wallet for on-chain asset handling and trustline management across PHP, USD, EUR, INR, GBP, and JPY. The integration is wired through StellarService and uses issuer public keys from environment configuration.

Security measures implemented

  • Transport & headers: Helmet (HSTS, no X-Powered-By), compression, strict CORS allowlist, trust proxy for correct client IPs behind a load balancer.
  • AuthN/AuthZ: Argon2id password hashing, JWT access (short TTL) + refresh token rotation with reuse detection, account lockout after repeated failed logins, optional TOTP-based MFA, global JwtAuthGuard (routes are private by default; opt out with @Public()).
  • Input hardening: global ValidationPipe with whitelist + forbidNonWhitelisted — unexpected/extra fields are rejected outright, not silently dropped.
  • Rate limiting: global default throttle plus tighter per-route throttles on auth and money-movement endpoints.
  • PII protection: AES-256-GCM field-level encryption for government ID numbers and MFA secrets; one-way hashing for values only ever compared (device fingerprints, refresh tokens at rest).
  • Error handling: a global exception filter ensures stack traces, SQL fragments, and internal paths are never returned to clients.
  • Idempotency: all money-movement endpoints require a client-supplied Idempotency-Key header, preventing duplicate processing on retries/timeouts.
  • Financial integrity: an append-only, double-entry ledger (LedgerEntry) backs every wallet balance change; balance mutations use optimistic locking (Wallet.version) inside serializable DB transactions with automatic retry on conflict.
  • Non-root containers: the production Docker image runs as an unprivileged user with a healthcheck and dumb-init for clean signal handling.

BSP / regulatory alignment (Philippines)

This scaffold is built with BSP Circular 1108 (E-Money Issuers) and the Anti-Money Laundering Act (AMLA) in mind. It is not a compliance certification — Nexa's compliance officer and counsel should review before go-live — but the following are wired in as a starting point:

Requirement Where it lives
Tiered KYC with balance/transaction ceilings KycTier enum, TierLimit table, KycTierGuard, limit checks in TransactionsService
Minimum age (18+) for account opening AuthService.register()
Covered Transaction Report threshold (≥ PHP 500,000) Transaction.requiresCtr, compliance.ctrThresholdPhp config
AML monitoring flag for high-value transfers Transaction.amlFlagged / HELD_FOR_REVIEW status
Immutable, append-only financial ledger LedgerEntry model (never updated/deleted)
Full audit trail (who/when/where) for security & financial events AuditLog model + AuditService, surfaced to users via /users/me self-service (Data Privacy Act right-to-access)
Encryption of sensitive personal/financial data at rest EncryptionService (AES-256-GCM) applied to ID numbers, MFA secrets
Long-tail record retention (BSP requires multi-year retention) soft-delete (deletedAt) rather than hard delete; compliance.auditLogRetentionDays config
InstaPay per-transaction ceiling (PPMI/BSP-set, revised periodically) compliance.instapayCeilingPhp config, enforced in TransactionsService.instapayTransfer()
Authenticity of inbound partner-bank fund notifications PartnerWebhookSignatureGuard (HMAC-SHA256, fails closed if unconfigured) on the virtual-account credit webhook

Before going live

  • Route HELD_FOR_REVIEW transactions to a real compliance/AML case-management workflow (this scaffold marks them but does not implement a review queue/UI).
  • Wire actual Suspicious Transaction Report (STR) and Covered Transaction Report (CTR) submission to the AMLC.
  • Add a formal admin/compliance-officer role and permission model (this MVP only has end-user auth).
  • Get an external security assessment / penetration test before handling real funds.
  • Confirm data residency, encryption-at-rest for the database itself, and backup/DR procedures meet BSP technology risk management guidelines.

Testing

npm test          # unit tests
npm run test:e2e  # end-to-end tests (spins up the full Nest app)
npm run test:cov  # coverage report

License

Proprietary — Techyra Private Ltd / Nexa. All rights reserved.

About

Core API for NexaPay

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages