Drupal case study · 04

Membership and Dues Management

A flexible membership system for organizations with different dues rules, renewal schedules, plan changes, and staff-support needs.

Problem
Some organizations renewed members on the anniversary of when they joined, while others used a common calendar renewal date. The same system also needed to work with payments and support different membership rules for each customer.
Built
Custom membership records and renewal workflows tied into the shared payment system, with configurable membership types, history, and staff tools.
Result
Different organizations could run their own membership models on one maintained platform while staff retained a clear history of what happened.
  • Content Entities
  • Config Entities
  • Plugin API
  • Queue API
  • Lock API
  • Database API
  • Views
  • Drush

Why we built it

Once the platform could collect payments, membership became a natural next capability. But membership was not just another checkout flow. Organizations needed to enroll members, calculate dues, renew them, change plans, handle failed payments, and give staff a trustworthy history when someone needed help.

I had already built an earlier member portal for a medical society with more than 3,000 members that ran for over ten years. That experience exposed the long-term problems a membership system has to handle, not just initial enrollment.

The new challenge was making those rules reusable. Some organizations renewed members on the anniversary of the day they joined, while others renewed everyone on a common calendar date. Different customers needed different membership types, prices, eligibility requirements, and renewal policies without turning each deployment into its own custom membership application.

My role

I designed and built the membership entities and their history, the pricing plugin system, lifecycle validation, scheduled renewal workers, the staff diagnostic and repair tools, and the integration with the shared payment service I had designed for the platform.

I also established the rule that every entry point had to go through the same lifecycle logic, whether the change came from a member form, scheduled worker, Drush command, or update hook. I reviewed the connected work across the rest of the product.

What it unlocked

Customer organizations could offer different membership products and policies while using one maintained platform capability. Members received a consistent experience across enrollment, renewal, and plan changes.

Staff had dashboards, history, simulation, and controlled repair tools, so they could understand the membership lifecycle itself instead of trying to reconstruct it from payment records.

What we added to the platform

Content entities stored each member’s current status and auditable history. Config entities described deployable membership types and policies. Pricing plugins handled calculations that varied among products without scattering customer-specific conditions through forms and renewal jobs. Focused services owned enrollment, renewal, plan changes, cancellation, and repair. Membership used the platform's shared payment service for dues and renewals rather than implementing its own payment integration.

Scheduled work ran outside member requests. Queue workers used Lock API and database transactions to prevent overlapping renewals, while a stable reference made each renewal safe to retry without creating a second charge. Drupal events notified mail, dashboards, and other features only after the official membership change completed. Forms, staff tools, and scheduled workers all used the same lifecycle services, so the same rules applied regardless of how a membership changed.

How renewal worked

Enrollment resolved the requested membership type, checked eligibility, and asked a pricing plugin to calculate the charge from server-controlled facts. After a verified payment, the membership became active and received its first effective period.

When renewal became due, a worker claimed that scheduled renewal, acquired a shared lock, reloaded the latest record, and rechecked the current policy. A confirmed payment extended the membership and recorded the financial result. A failed or uncertain result followed the customer’s policy and remained visible for staff attention. Future plan changes were also rechecked when they became effective because eligibility and configuration might have changed.

Drupal under the hood

Content and Config Entity APIs
Separated member-specific records from deployable product and policy configuration.
Plugin and Validation APIs
Provided configurable pricing while protecting eligibility and data rules in every entry path.
Queue, Lock, and Database APIs
Processed renewals outside member requests and protected critical financial changes.
Views and Drush
Supported staff dashboards, exports, read-only audits, simulations, and controlled repair.

Why I chose this approach

I chose to build our own membership system because the organizations we worked with had very different membership rules.

Some memberships renewed each year on the anniversary of when someone joined. Others used a common calendar renewal date. Membership also needed to work directly with our payment system and eventually influence other parts of the platform, such as access to courses or community groups.

Building our own membership records and renewal process gave us control over those relationships instead of trying to force different customer requirements into a simpler membership model.

It was a more complex system to build, but it gave us the flexibility to support different organizations on the same platform and continue extending membership as new needs came up.