> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nuevesolutions.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Member Experience

## Overview

This page walks through the end-to-end member journey for the Banno integration, screen by screen. The journey covers both roles:

* **Acceptor** (new member) — lands on the Banno dashboard, discovers the RAF tile, submits a referral code, and tracks eligibility.
* **Inviter** (existing member) — shares a permanent personal code via email, SMS, or social, and watches joined / rewarded counts increment.

Every screen below is served by the Django plugin embedded inside the Banno dashboard as an iframe.

***

## 1. Plugin Tile — Acceptor Entry (`/plugin/`)

<img src="https://mintcdn.com/nueve-dfd2a7f8/OP4-IsZ9mjBBJ_aP/images/refer-a-friend/banno/raf_banno_flow1.png?fit=max&auto=format&n=OP4-IsZ9mjBBJ_aP&q=85&s=950be8428ab2d9be573d0ebbd716a218" alt="Banno Garden dashboard with the Refer-a-Friend plugin tile showing an acceptor-entry state, with a banner saying 'Click here to submit the referral code'." style={{ maxWidth: '760px', display: 'block', margin: '24px auto' }} width="1885" height="955" data-path="images/refer-a-friend/banno/raf_banno_flow1.png" />

The `/plugin/` endpoint returns a server-rendered tile that renders inside the Banno dashboard iframe. The tile is **dynamic** — it changes based on the RAF state of the signed-in member.

| Plugin State        | Shown When                                                                                                                    |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `anonymous`         | No valid `raf_access_token` cookie. Shows the generic "Refer a friend" artwork plus a banner linking to the submit-code flow. |
| `inviter_ready`     | Member has a permanent code and no pending acceptor role. Shows `Joined N` and an **Invite Now** button.                      |
| `acceptor_pending`  | Code submitted, criteria not yet met. Shows "Click here to check eligibility status".                                         |
| `acceptor_eligible` | All criteria met; reward awaiting payment.                                                                                    |
| `acceptor_paid`     | Reward paid; terminal state.                                                                                                  |

Clicking anywhere on the tile routes to `/auth/`, which starts the Banno OIDC flow.

***

## 2. Submit Referral Code — Entry (`/refer/`)

<img src="https://mintcdn.com/nueve-dfd2a7f8/OP4-IsZ9mjBBJ_aP/images/refer-a-friend/banno/raf_banno_flow2.png?fit=max&auto=format&n=OP4-IsZ9mjBBJ_aP&q=85&s=8657791d77cede06aaf26d69412f7de2" alt="Refer-a-Friend landing page with the Submit a Referral Code modal open, an invalid example code 'ABC123' typed into the input." style={{ maxWidth: '760px', display: 'block', margin: '24px auto' }} width="1890" height="1021" data-path="images/refer-a-friend/banno/raf_banno_flow2.png" />

After the member signs in through Banno, they land on the full RAF page. New members see the **Submit a Referral Code** modal with the program description on the left and the code input on the right.

The call on submit is:

```
POST /api/referrals/code/
Authorization: Bearer <internal-jwt>
Content-Type: application/json

{ "referral_code": "ABC123" }
```

Validation runs server-side through `referral_service.py` and includes:

* Code exists and is active.
* Acceptor ≠ inviter (no self-referral).
* Acceptor has not already submitted a code in this campaign.
* Acceptor SSN hash does not match the inviter's SSN hash (blocks same person across multiple OLB accounts).
* Acceptor SSN hash has not been seen on a prior submission.
* No mutual code reuse.
* An active campaign matches.

***

## 3. Valid Code Entered

<img src="https://mintcdn.com/nueve-dfd2a7f8/OP4-IsZ9mjBBJ_aP/images/refer-a-friend/banno/raf_banno_flow3.png?fit=max&auto=format&n=OP4-IsZ9mjBBJ_aP&q=85&s=f679a2a5a2971f87eec706832759c323" alt="Same Submit a Referral Code modal with a valid 6-character code 'VZDWBK' entered, ready to submit." style={{ maxWidth: '760px', display: 'block', margin: '24px auto' }} width="1890" height="1016" data-path="images/refer-a-friend/banno/raf_banno_flow3.png" />

Codes are 6-character alphanumeric, case-insensitive, and validated against `Member.code` scoped by `tenant`. The client does not pre-validate — the server performs the full validation cascade on submit.

***

## 4. Code Accepted

<img src="https://mintcdn.com/nueve-dfd2a7f8/OP4-IsZ9mjBBJ_aP/images/refer-a-friend/banno/raf_banno_flow4.png?fit=max&auto=format&n=OP4-IsZ9mjBBJ_aP&q=85&s=ebea245f388ae22cb80a02531c7ce9f0" alt="Submit a Referral Code modal showing a green success banner: 'Referral code accepted for 1 campaign(s).'" style={{ maxWidth: '760px', display: 'block', margin: '24px auto' }} width="1895" height="1016" data-path="images/refer-a-friend/banno/raf_banno_flow4.png" />

On success, the API responds with:

```json theme={null}
{
  "success": true,
  "data": {
    "campaigns_matched": 1,
    "referrals_created": [
      {
        "referral_id": 4217,
        "campaign_name": "Auto Loan Summer Push",
        "ccw_expires_at": "2026-06-07",
        "status": "submitted"
      }
    ]
  },
  "error": ""
}
```

A `Referral` row is created with `status = 'submitted'`, `ccw_expires_at = submitted_at + campaign.ccw_days`, and `eligibility_progress` initialized to per-criterion `false`.

If multiple active campaigns match, one `Referral` row is created per matching campaign.

***

## 5. Inviter Share Page

<img src="https://mintcdn.com/nueve-dfd2a7f8/OP4-IsZ9mjBBJ_aP/images/refer-a-friend/banno/raf_banno_flow5.png?fit=max&auto=format&n=OP4-IsZ9mjBBJ_aP&q=85&s=75641621d66459441215631d012362ea" alt="Refer-a-Friend full page for an existing member showing 'You'll Both Earn $100', the inviter's permanent code '1TUXR0', email/social tabs, and share buttons for Facebook, WhatsApp, X, and copy-link." style={{ maxWidth: '760px', display: 'block', margin: '24px auto' }} width="1891" height="1018" data-path="images/refer-a-friend/banno/raf_banno_flow5.png" />

Existing members see their permanent code (`1TUXR0` in the example) plus share options:

* **Email** — the app composes an invite email via `email_service.py` and hands off to SMTP / SendGrid based on `EMAIL_PROVIDER`.
* **Social media** — pre-filled share URLs for Facebook, WhatsApp, X, and a copy-to-clipboard fallback.

The code is generated on first sign-in via `referral_code_service.get_or_create_code()` and is **permanent per member** — the same code is reused across every campaign for that member.

***

## 6. Plugin Tile — Inviter State

<img src="https://mintcdn.com/nueve-dfd2a7f8/OP4-IsZ9mjBBJ_aP/images/refer-a-friend/banno/raf_banno_flow6.png?fit=max&auto=format&n=OP4-IsZ9mjBBJ_aP&q=85&s=ecba3ea0ad713e5d3bef5553622dd5b7" alt="Banno Garden dashboard showing the Refer-a-Friend plugin tile in inviter mode: headline 'Refer a friend — You both get rewards', 'Joined 0' counter, an Invite Now button, and a link 'Click here to check eligibility status'." style={{ maxWidth: '760px', display: 'block', margin: '24px auto' }} width="1799" height="946" data-path="images/refer-a-friend/banno/raf_banno_flow6.png" />

When a member both (a) has a permanent code and (b) has a pending acceptor referral of their own, the plugin tile reflects both roles: the inviter call-to-action **Invite Now** sits above a secondary link to the acceptor eligibility status page.

The `Joined N` counter is the number of acceptors who have submitted this member's code and are currently in `submitted`, `criteria_met`, `ready_to_pay`, or `paid` status (excludes `expired`).

***

## 7. Reward Eligibility Status

<img src="https://mintcdn.com/nueve-dfd2a7f8/OP4-IsZ9mjBBJ_aP/images/refer-a-friend/banno/raf_banno_flow7.png?fit=max&auto=format&n=OP4-IsZ9mjBBJ_aP&q=85&s=5f76675b2a3380d4521c9335ab2116a5" alt="Refer-a-Friend Reward Eligibility Status page showing 'Auto Loan Summer Push — SUBMITTED', criteria window closing June 7, 2026, and one criterion: 'Open a consumer auto loan account' with a warning icon indicating not yet met." style={{ maxWidth: '760px', display: 'block', margin: '24px auto' }} width="1905" height="686" data-path="images/refer-a-friend/banno/raf_banno_flow7.png" />

Acceptors can track progress at `/dashboard/eligibility/`. For each referral the page shows:

* Campaign name and current `status` badge (`SUBMITTED`, `CRITERIA MET`, `READY TO PAY`, `PAID`, `EXPIRED`).
* `ccw_expires_at` — the deadline to meet all criteria ("Criteria window closes: June 7, 2026").
* A per-criterion checklist rendered from `Referral.eligibility_progress`. Each criterion shows as **met** (green check) or **not met** (orange warning).

Data flow:

1. `/api/eligibility/` is called from the page.
2. If a valid Banno access token is in the server-side cache → `eligibility_service.py` runs the **live** path: calls the Banno Consumer API, re-evaluates `criteria_config.logic`, persists the result.
3. If no valid Banno token → the **cached** path returns the last known `eligibility_progress` JSON from the `Referral` row.

***

## 8. Program Terms & Conditions

<img src="https://mintcdn.com/nueve-dfd2a7f8/OP4-IsZ9mjBBJ_aP/images/refer-a-friend/banno/raf_banno_flow8.png?fit=max&auto=format&n=OP4-IsZ9mjBBJ_aP&q=85&s=ea3092d6d541147e9ae9ae43d558dc98" alt="Official Program Disclosures page for the Refer-a-Friend program. Sections labeled 'Referring Member & Referred Member' and 'Referring Member' with legal bullet points about eligibility, HSA exclusion, good standing, and tax implications." style={{ maxWidth: '760px', display: 'block', margin: '24px auto' }} width="1892" height="1021" data-path="images/refer-a-friend/banno/raf_banno_flow8.png" />

A static disclosures page is bundled with the plugin and linked from both the inviter share page ("Click here for full terms and conditions") and the eligibility status page. Typical content:

* Qualifying account types (consumer checking only; HSAs excluded).
* Good-standing requirement — no negative balances during the promotional period.
* Forfeiture clause if the referred member closes the account within six months.
* Self-referral prohibition and anti-abuse language.
* Tax treatment — bonuses are reportable on 1099 forms.

The content is FI-editable; copy is supplied during onboarding and dropped into the template under `InviteFriend/templates/InviteFriend/terms.html`.

***

## Supporting API Calls

| Screen                 | Endpoint                         | Purpose                                              |
| ---------------------- | -------------------------------- | ---------------------------------------------------- |
| Plugin tile            | `GET /plugin/`                   | Server-rendered tile; reads cookie JWT to pick state |
| Auth start             | `GET /auth/`                     | Build PKCE + state, 302 to Banno                     |
| Auth callback          | `GET /oidc/callback/`            | Validate state, exchange code, set cookie            |
| Dashboard / refer page | `GET /dashboard/`, `GET /refer/` | Authenticated HTML pages                             |
| Submit code            | `POST /api/referrals/code/`      | Create `Referral` after full validation cascade      |
| Invite email           | `POST /api/referrals/invite/`    | Send invite via configured email provider            |
| Eligibility            | `GET /api/eligibility/`          | Live or cached criteria evaluation                   |
| Reward state           | `GET /api/rewards/status/`       | Current state of each referral's reward              |
| Accounts               | `GET /api/accounts/`             | Account list from Banno Consumer API                 |
