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_priceas
an optional dependency incrm_membership.info.ymlso 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_pricefield
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\PriceItemand
\Drupal\commerce_price\Pricevalue object (number as string +
currency code) for calculations and display; use
commerce_price.rounderwhen 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 typecommerce_priceto 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.
- Membership type (config): Optionally add a config
- API: Provide getter/setter helpers on the relevant
entities that return/acceptPriceobjects where a price is
stored, and document that callers should depend on
commerce_pricewhen using those APIs.
Remaining tasks
- Add
commerce_pricedependency (optional or required) to
crm_membership.info.yml. - Add Commerce Price base field(s) to the appropriate entity(ies)
(e.g.crm_membership_periodfor “amount” or “dues”). - If supporting default dues on membership types, add config schema and
storage for price (amount + currency) oncrm_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.rounderand the Price value object. - Document dependency on
commerce_pricefor code paths that
use price fields or Price objects.
Data model changes
- Content entities (e.g.
crm_membership_period):
New base field of typecommerce_price(e.g.price
ordues) 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
Comment #2
jdleonardI'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?
Comment #3
bluegeek9 commentedDo you see crm_membership not have price as a feature?
Comment #4
jdleonardMemberships 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.
Comment #5
svendecabooterFor 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?
Comment #6
svendecabooterHas 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.
Comment #7
svendecabooterSo 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.
Comment #8
jdleonardI need to find time to digest
crm_membership_commercemore, 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_commerceprovides 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.
Comment #9
mortona2k commentedI 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.