Skip to main content
The Banno integration is the Refer-a-Friend (RAF) implementation for financial institutions running on Banno Digital Platform (Jack Henry). Unlike the Candescent implementation — which embeds a CDX React widget into the Candescent OLB shell — the Banno implementation is delivered as a Django-backed iframe plugin that loads inside the Banno dashboard as a “plugin card” tile. The application is single-codebase, multi-tenant: one deployment serves multiple FIs. Each FI is onboarded as a Tenant row and isolated by platform='banno' and tenant-scoped queries.

How It Differs from Candescent

AspectCandescentBanno
DeliveryCDX React widget via Module FederationServer-rendered HTML loaded in iframe (Banno plugin)
FrameworkReact 18 + MUI 7 + TypeScriptDjango 6.0 + Python 3.12
Member AuthCandescent JWT forwarded by OLB shellBanno OIDC + PKCE → internal HS256 JWT cookie
Account/Transaction DataCandescent DevEx REST APIsBanno Consumer API (OAuth-protected)
FI Admin UICandescent admin portalRAF Admin Console (bundled Django app InviteFriendConsole)
Token HandlingForwarded Authorization headerBanno tokens cached server-side, never sent to client

Solution Capabilities

The Banno integration delivers the same RAF capabilities documented in the product introduction, plus a few Banno-specific ones:
  • Iframe plugin tile with five dynamic states (anonymous, inviter_ready, acceptor_pending, acceptor_eligible, acceptor_paid) rendered server-side at /plugin/
  • Banno OIDC + PKCE sign-in flow, state-token CSRF protection, and server-side token cache
  • Internal short-lived JWT (600s HS256, httpOnly cookie) with silent re-issue while the Banno session is still valid
  • Live eligibility checks that call the Banno Consumer API on demand, with a cached fallback when the Banno access token is absent
  • FI staff console with campaign CRUD, referral list, per-referral eligibility detail, and pay/mark-as-paid actions
  • Provider abstraction (services/banking/providers/) that makes it possible to swap Banno for another provider (Q2 already lives alongside Banno) without touching the RAF business logic

Technology Stack

Application
  • Django 6.0.2 on Python 3.12
  • Django templates server-rendered (no SPA)
  • requests for outbound HTTP to Banno
  • PyJWT for internal JWT issuance and validation
Database
  • SQLite in development
  • PostgreSQL in production (via DB_* environment variables)
Auth
  • Banno OIDC + PKCE (RFC 7636) for member sign-in
  • Django session + staff login for the admin console
  • Internal HS256 JWT signed with Django SECRET_KEY, stored in the raf_access_token httpOnly cookie
Deployment
  • Any WSGI/ASGI server (gunicorn, uvicorn)
  • Must be reachable over HTTPS from Banno (ngrok in dev; TLS-terminating load balancer in prod)
  • Content-Security-Policy: frame-ancestors 'self' <BANNO_FI_URL> is set by middleware so the plugin tile can be embedded by the FI’s Banno domain

Integration Points

Banno platform
  • OIDC authorization endpoint: GET {FI_URL}/a/consumer/api/v0/oidc/auth
  • Token endpoint: POST {FI_URL}/a/consumer/api/v0/oidc/token
  • Consumer API (accounts, transactions): GET {FI_URL}/a/consumer/api/v0/users/{userId}/...
RAF application
  • Plugin tile and dashboard views — rendered by InviteFriend/views.py
  • Stateless JSON API (Bearer JWT) — InviteFriend/api_views.py
  • Campaign / referral / eligibility / email / payment services — InviteFriend/*_service.py
  • FI admin console — InviteFriendConsole/