Skip to content

Instantly share code, notes, and snippets.

@jghankins
Created March 5, 2026 15:42
Show Gist options
  • Select an option

  • Save jghankins/654dc2f44f269fe0e88efad017489f06 to your computer and use it in GitHub Desktop.

Select an option

Save jghankins/654dc2f44f269fe0e88efad017489f06 to your computer and use it in GitHub Desktop.
Pewpros Groups Feature Guide - How group seats, invitations, roles, and Stripe purchasing work

Pewpros Groups Feature Guide

Overview

Groups let instructors sell team/organization access to courses. An organization buys a block of seats, gets a group, and can invite members who automatically get access to all linked courses.

Key advantage over LifterLMS: A single group can grant access to multiple courses (LifterLMS limits to one).

How It Works

1. Creating a Group

Admin UI: Navigate to /admin/groups → New Group. Fields:

  • Name / Description / Slug (auto-generated)
  • Total Seats — max members allowed
  • Visibilityprivate (invite only), open (join link), or closed (no new members)
  • Pricing Modelper_seat (price × quantity) or fixed_tier (flat price includes N seats)
  • Stripe Price ID — links to a Stripe Price for checkout

Via API: POST /api/v1/groups with Bearer token authentication.

Via Stripe Purchase: A customer buys group access at /courses/:slug/group-purchase, picks seat count, names the group, and completes Stripe Checkout. The webhook automatically creates the group, makes the purchaser primary_admin, and links the course(s).

2. Linking Courses to a Group

Admin UI: On the group show page (/admin/groups/:id), scroll to "Add Course" — it lists all courses not yet linked. Click "Add" to link one.

Via Stripe: The group purchase checkout passes course_ids in Stripe metadata. On successful payment, those courses are auto-linked.

Note: Only courses are linkable to groups. Events are not currently supported.

3. Roles & Permissions

Four roles with hierarchical permissions:

Role Manage Members Manage Managers Manage Info Manage Seats View Reports Delete Group
primary_admin
admin
leader
member
  • The group creator (or Stripe purchaser) is always primary_admin
  • primary_admin cannot be demoted
  • Admins can change roles for leaders/members
  • Leaders can invite/remove members but can't manage other leaders/admins

4. Inviting Members

Two invitation types:

Email Invitations:

  • Admin enters one or more emails (comma/space/newline separated) → batch invitations sent
  • Each invited person receives an email with an "Accept Invitation" link
  • The invitation is locked to that email address — only the matching account can accept
  • Pending email invitations count toward seat usage (reserving the seat)
  • Default expiry: 7 days

Open Invitations (Join Links):

  • Generate a shareable URL at /groups/join/:token
  • Anyone with the link can join (no email matching)
  • One active open invitation per group at a time
  • Open invitations do NOT count toward seat usage (seats are consumed on accept)
  • Default expiry: 1 year
  • Can be disabled/re-enabled by toggling

5. Accepting an Invitation

When a user visits /groups/join/:token:

  1. Token is looked up → shows error if not found, expired, or already used
  2. For email invitations: logged-in user's email must match the invitation email
  3. Checks if user is already a member → shows "Already a Member" if so
  4. Checks seat availability → shows "Group Full" if no seats left
  5. If everything checks out → shows group info + "Accept & Join" button
  6. On accept: creates an active seat for the user with member role, redirects to leader dashboard

6. Stripe Group Purchase Flow

Route: /courses/:slug/group-purchase

  1. User selects seat count (or picks a tier) and names the group
  2. Creates a Stripe Checkout Session with metadata:
    • purchase_type: "group"
    • group_name, group_seats, course_ids
    • Supports both payment (one-time) and subscription modes
  3. On successful payment (webhook checkout.session.completed):
    • Creates the group with specified seats
    • Adds purchaser as primary_admin
    • Links specified courses
    • Stores stripe_subscription_id if subscription mode

7. Group Leader Dashboard

Route: /my/groups (index) and /my/groups/:slug (manage)

Available to users with primary_admin, admin, or leader role.

Index page shows:

  • All groups where user has leader+ role
  • Member count / total seats with progress bar
  • Linked course count, pending invitation count

Show page has tabbed sections:

  • Overview — group details, visibility, join link management (generate/copy/disable)
  • Members — list with role badges, role change dropdowns, remove buttons
  • Invitations — send batch invitations via textarea, list pending with revoke buttons
  • Courses — linked courses with status badges

8. REST API Endpoints

All endpoints require Bearer token authentication (Authorization: Bearer <token>).

Method Path Description
GET /api/v1/groups List user's groups
POST /api/v1/groups Create a group
GET /api/v1/groups/:id Get group details
PATCH /api/v1/groups/:id Update group
DELETE /api/v1/groups/:id Delete group
POST /api/v1/groups/:token/accept-invitation Accept invitation by token
GET /api/v1/groups/:group_id/members List members
GET /api/v1/groups/:group_id/members/:id Get member details
PATCH /api/v1/groups/:group_id/members/:id Change member role
DELETE /api/v1/groups/:group_id/members/:id Remove member
GET /api/v1/groups/:group_id/invitations List invitations
POST /api/v1/groups/:group_id/invitations Create invitation(s)
DELETE /api/v1/groups/:group_id/invitations/:id Revoke invitation
GET /api/v1/groups/:group_id/seats Get seat info (total/used/available)
PUT /api/v1/groups/:group_id/seats Update total seats

9. Seat Counting Logic

  • Used seats = active seats + pending email invitations (open invitations don't reserve seats)
  • Available seats = total_seats - used_seats
  • A group is "full" when available_seats <= 0
  • Changing total_seats can be done by users with manage_seats permission

10. Key Files

Area Files
Schema lib/pewpros/groups/group.ex, group_seat.ex, group_invitation.ex
Context lib/pewpros/groups.ex
Email lib/pewpros/emails/group_invitation_email.ex
Worker lib/pewpros/workers/group_invitation_worker.ex
Join Page lib/pewpros_web/live/group_live/join.ex + .html.heex
Purchase lib/pewpros_web/live/group_live/purchase.ex + .html.heex
Dashboard lib/pewpros_web/live/group_live/dashboard/{index,show}.ex + .html.heex
API lib/pewpros_web/controllers/api/v1/group_{controller,member_controller,invitation_controller,seat_controller}.ex
Admin lib/pewpros_web/live/admin/group_live/{show,form_component}.ex
Auth Plug lib/pewpros_web/plugs/group_authorization.ex
Billing lib/pewpros/billing.ex (group checkout + webhook handler)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment