Problem/Motivation

CRM Membership tracks membership information (membership types, memberships, and
membership periods) but has no way to track monetary amounts for payments such as
dues, fees, or renewals. To support payment tracking and future integration with
Commerce orders or payment gateways, we need a consistent, currency-aware way to
store and display prices.

Using custom decimal/currency fields would duplicate logic and diverge from
Drupal ecosystem standards. The Commerce module’s Price system provides a
battle-tested value object (number + currency code), rounding per currency, and
reuse across product pricing, orders, and payments.

Steps to reproduce

  • N/A — this is a feature request.

Proposed resolution

Integrate the Commerce Price module
(commerce_price) so that membership-related amounts use the
Commerce Price field type and value object.

  • Dependency: Add commerce:commerce_price as
    an optional dependency in crm_membership.info.yml so the module
    can run without Commerce when price features are not needed; or a required
    dependency if the project always wants price support.
  • Field type: Use the commerce_price field
    type (PriceItem) for any field that stores a monetary amount (e.g. dues,
    fee, amount paid).
  • Value handling: Use the
    \Drupal\commerce_price\Plugin\Field\FieldType\PriceItem and
    \Drupal\commerce_price\Price value object (number as string +
    currency code) for calculations and display; use
    commerce_price.rounder when rounding is required.
  • Placement of price fields:
    • Membership type (config): Optionally add a config
      schema and stored config for a “default dues price” (amount + currency)
      per membership type, so renewal forms or order creation can default
      amounts. Config entities cannot have full Field API fields, so this would
      be stored as structured config (e.g. default_dues_amount,
      default_dues_currency) or via a dedicated config schema
      matching Commerce Price structure.
    • Membership period (content): Add a base field (or
      optional configurable field) of type commerce_price to store
      the amount paid or charged for that period (e.g. dues for that period).
      This allows reporting and display per period and aligns with
      Commerce/order data if we later link orders to periods.
  • API: Provide getter/setter helpers on the relevant
    entities that return/accept Price objects where a price is
    stored, and document that callers should depend on
    commerce_price when using those APIs.

Remaining tasks

  • Add commerce_price dependency (optional or required) to
    crm_membership.info.yml.
  • Add Commerce Price base field(s) to the appropriate entity(ies)
    (e.g. crm_membership_period for “amount” or “dues”).
  • If supporting default dues on membership types, add config schema and
    storage for price (amount + currency) on crm_membership_type.
  • Update entity forms to display and validate Commerce Price field(s)
    (using Commerce’s form elements/widgets where applicable).
  • Expose price in views (Views integration for Commerce Price if needed).
  • Add/update tests for price storage, rounding, and display.
  • Document the new dependency and any configuration in the module’s
    README or docs.

User interface changes

  • Membership period add/edit forms: show a price field (amount + currency)
    for the period’s dues or amount paid, using Commerce Price widget/format.
  • Optional: membership type form shows a “default dues” price used when
    creating or renewing periods.
  • List and view displays for membership periods (and optionally membership
    types) show the price in the configured currency format.

API changes

  • New or updated entity methods that work with
    \Drupal\commerce_price\Price (e.g.
    MembershipPeriod::getPrice() / setPrice(Price $price)).
  • Services that need to round or calculate totals should type-hint and use
    commerce_price.rounder and the Price value object.
  • Document dependency on commerce_price for code paths that
    use price fields or Price objects.

Data model changes

  • Content entities (e.g. crm_membership_period):
    New base field of type commerce_price (e.g. price
    or dues) storing number and currency code via Commerce Price
    storage.
  • Config entities (e.g. crm_membership_type):
    If default dues are supported, extend config export and schema with
    price-related keys (amount + currency) compatible with Commerce Price
    semantics; no new database tables from Commerce, only new columns or config
    keys.
  • No change to existing membership or membership type keys; additive only.

Comments

bluegeek9 created an issue.

jdleonard’s picture

I'm against Commerce Price being a required dependency. Best if base membership functionally is not dependent on the Commerce project / it is easy to have other options for this functionality.

Ultimately I envision folks composing the functionally they need, often with recipes. Perhaps this feature would make sense as a separate project, which could have the required dependency? Perhaps an interface / plug-in system in the base Membership project might make sense?

bluegeek9’s picture

Do you see crm_membership not have price as a feature?

jdleonard’s picture

Memberships having prices is clearly going to be in demand. My hesitation is in adopting Commerce Price as the only way to capture that. I'd be less concerned if Commerce Price were in its own project without a dependency on the hefty commerce project.

If a meetup group doesn't have paid options, it would be best if it didn't need to lug Commerce around and be dependent on prompt releases of the Commerce project for each major Drupal release.

svendecabooter’s picture

For my project I have created a crm_membership_commerce module that provides a product type to sell memberships. A product references a specific membership type, and has an associated price field provided by Drupal Commerce.

Upon buying the product, a membership of the specified type gets activated.

There is still some project-specific logic in there. I'll try to release it as a module if it's made more general and there is some interest in it.

So I would propose to keep the price and commerce stuff separate from the membership logic itself.

That wouldn't help if you want to specify membership prices without a commerce system around it obviously, but I'm not sure if that's a popular setup? Usually membership prices would go hand in hand with paying for them I guess?

svendecabooter’s picture

Has some time today to clean up my CRM membership <> Commerce integration module.
I've added it to https://www.drupal.org/project/crm_membership_commerce
Feedback welcome as always.

svendecabooter’s picture

So to clarify how this relates to this issue:

In my setup I use the module above to create a Membership product that can be purchased, and has a price set on the product itself. When purchased, it grants users membership.
So that alleviates the need to add a pricing field to the membership entity itself, since I handle that via commerce products.

jdleonard’s picture

I need to find time to digest crm_membership_commerce more, but my first impression is that this is a great direction. Thank you Sven!

There are going to be organizations that want to manage pricing somewhere other than CRM Membership (i.e. where the payment is taken). crm_membership_commerce provides one example of how to do so. Other possible examples include any external payment system or a simple Stripe integration.

There may be a subset of payment systems / organizations that would benefit from CRM Membership entities being the source of truth for the pricing. Perhaps there should be a "CRM Membership Price" module (name might be improved) that serves those use cases?

I propose that this issue be converted to pursue that.

mortona2k’s picture

I like the way CRM Membership Commerce works. It makes sense to have the price on a commerce product that references a membership type, if using Commerce.

If someone wants to add a Commerce Price field to their membership type, they can install the module and do that.

If someone wants to implement an alternative to Commerce, maybe they don't want to use Commerce Price? It is a robust field and does make sense to use, but they could have other logic required.

If we do want to have a standard price field implemented for membership types that other modules can be based on, using Commerce Price, then a submodule makes sense. However, I don't see a clear value in defining a price that's not part of an implementation that uses it.

If CRM Membership Commerce depends on CRM Membership Price, it would have to do additional work to use the price on membership type for the commerce product.