Skip to content

Twig Extensions ​

The ICoTA Members plugin provides custom Twig functions for accessing purchase and membership data in templates.

Overview ​

Location: src/twigextensions/PurchaseExtension.php

The PurchaseExtension registers custom Twig functions that allow you to query membership and purchase data directly from templates.

Available Functions ​

Purchase Functions ​

getUserPurchases ​

Get all purchases for a specific user.

twig
{% set purchases = getUserPurchases(currentUser) %}

{% for purchase in purchases %}
  <div class="purchase">
    <p>Amount: {{ purchase.amount / 100 }} {{ purchase.currency|upper }}</p>
    <p>Status: {{ purchase.status }}</p>
    <p>Date: {{ purchase.dateCreated|date('Y-m-d') }}</p>
  </div>
{% endfor %}

Parameters:

  • user (User) - Craft User element

Returns: Array of PurchaseRecord objects


getUserPurchasesById ​

Get purchases by user ID.

twig
{% set purchases = getUserPurchasesById(123) %}

Parameters:

  • userId (int) - User ID

Returns: Array of PurchaseRecord objects


getUserPurchasesByEmail ​

Get purchases by email address.

twig
{% set purchases = getUserPurchasesByEmail('user@example.com') %}

Parameters:

  • email (string) - Email address

Returns: Array of PurchaseRecord objects

Note: This function attempts to find the user first, then falls back to querying Stripe directly if no user is found.


getUserPurchaseSummary ​

Get aggregated purchase statistics for a user.

twig
{% set summary = getUserPurchaseSummary(currentUser) %}

<div class="purchase-summary">
  <p>Total Purchases: {{ summary.total_purchases }}</p>
  <p>Total Amount: {{ summary.total_amount / 100 }}</p>

  <h3>By Status</h3>
  {% for status, data in summary.by_status %}
    <p>{{ status|title }}: {{ data.count }} purchases ({{ data.amount / 100 }})</p>
  {% endfor %}

  <h3>By Currency</h3>
  {% for currency, data in summary.by_currency %}
    <p>{{ currency|upper }}: {{ data.count }} purchases ({{ data.amount / 100 }})</p>
  {% endfor %}
</div>

Parameters:

  • user (User) - Craft User element

Returns: Array with structure:

php
[
    'total_purchases' => 5,
    'total_amount' => 50000,  // in cents
    'by_status' => [
        'succeeded' => ['count' => 4, 'amount' => 45000],
        'refunded' => ['count' => 1, 'amount' => 5000]
    ],
    'by_currency' => [
        'usd' => ['count' => 5, 'amount' => 50000]
    ]
]

getUserPurchaseSummaryById ​

Get purchase summary by user ID.

twig
{% set summary = getUserPurchaseSummaryById(123) %}

Parameters:

  • userId (int) - User ID

Returns: Same structure as getUserPurchaseSummary


getUserPurchaseSummaryByEmail ​

Get purchase summary by email address.

twig
{% set summary = getUserPurchaseSummaryByEmail('user@example.com') %}

Parameters:

  • email (string) - Email address

Returns: Same structure as getUserPurchaseSummary


getCustomerPurchases ​

Get purchases for a specific Stripe customer ID.

twig
{% set purchases = getCustomerPurchases('cus_abc123') %}

Parameters:

  • stripeCustomerId (string) - Stripe customer ID

Returns: Array of PurchaseRecord objects


getCustomerPurchaseSummary ​

Get purchase summary for a Stripe customer ID.

twig
{% set summary = getCustomerPurchaseSummary('cus_abc123') %}

Parameters:

  • stripeCustomerId (string) - Stripe customer ID

Returns: Array with structure:

php
[
    'customer_id' => 'cus_abc123',
    'total_purchases' => 3,
    'total_amount' => 30000,
    'by_status' => [...],
    'by_currency' => [...]
]

Membership Functions ​

getUserMemberships ​

Get all memberships for a user (active, expired, and cancelled).

twig
{% set memberships = getUserMemberships(currentUser) %}

{% for membership in memberships %}
  <div class="membership">
    <p>Status: {{ membership.status|title }}</p>
    <p>Expires: {{ membership.expiryDate|date('Y-m-d') }}</p>
    {% if membership.chapterId %}
      {% set chapter = craft.app.sites.getSiteByUid(membership.chapterId) %}
      <p>Chapter: {{ chapter.name }}</p>
    {% endif %}
  </div>
{% endfor %}

Parameters:

  • user (User) - Craft User element

Returns: Array of MembershipRecord objects


getUserActiveMemberships ​

Get only active memberships for a user.

twig
{% set activeMemberships = getUserActiveMemberships(currentUser) %}

{% if activeMemberships|length > 0 %}
  {% set membership = activeMemberships|first %}
  <div class="alert alert-success">
    Your membership is active until {{ membership.expiryDate|date('F j, Y') }}
  </div>
{% endif %}

Parameters:

  • user (User) - Craft User element

Returns: Array of active MembershipRecord objects

Filters:

  • Status: active
  • Start date: null or in the past
  • Expiry date: in the future

userHasActiveMembership ​

Check if a user has any active membership (boolean check).

twig
{% if userHasActiveMembership(currentUser) %}
  <div class="member-content">
    {# Premium content for members #}
  </div>
{% else %}
  <div class="join-prompt">
    <a href="/join">Become a member</a>
  </div>
{% endif %}

Parameters:

  • user (User) - Craft User element

Returns: Boolean (true if user has active membership)


Debug Functions ​

Debugging Only

These functions should only be used during development and should be removed or disabled in production.

debugUserPurchases ​

Debug function to troubleshoot purchase lookup issues.

twig
{# Only use in development! #}
{% if craft.app.config.general.devMode %}
  {% set debug = debugUserPurchases(currentUser) %}
  {{ dump(debug) }}
{% endif %}

Parameters:

  • user (User) - Craft User element

Returns: Array with debug information:

php
[
    'user_id' => 123,
    'user_email' => 'user@example.com',
    'stripe_plugin_exists' => true,
    'stripe_customer_count' => 2,
    'stripe_customers' => [...],
    'total_purchases_in_db' => 150,
    'purchases_linked_to_user' => 5,
    'sample_purchases' => [...]
]

Security Note: This function exposes internal system information and should be disabled in production.


Usage Examples ​

Display Member Status Dashboard ​

twig
{% set user = currentUser %}

{# Check membership status #}
{% if userHasActiveMembership(user) %}
  {% set activeMemberships = getUserActiveMemberships(user) %}
  {% set membership = activeMemberships|first %}

  <div class="membership-status">
    <h2>Your Membership</h2>
    <p class="status active">Active</p>
    <p>Expires: {{ membership.expiryDate|date('F j, Y') }}</p>

    {% if membership.chapterId %}
      {% set chapter = craft.app.sites.getSiteByUid(membership.chapterId) %}
      <p>Chapter: {{ chapter.name }}</p>
    {% endif %}
  </div>
{% else %}
  <div class="membership-status">
    <h2>No Active Membership</h2>
    <p><a href="/join" class="button">Join Now</a></p>
  </div>
{% endif %}

Display Purchase History ​

twig
{% set user = currentUser %}
{% set purchases = getUserPurchases(user) %}
{% set summary = getUserPurchaseSummary(user) %}

<div class="purchase-history">
  <h2>Purchase History</h2>

  <div class="summary">
    <p>Total Purchases: {{ summary.total_purchases }}</p>
    <p>Total Spent: ${{ (summary.total_amount / 100)|number_format(2) }}</p>
  </div>

  <table>
    <thead>
      <tr>
        <th>Date</th>
        <th>Amount</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      {% for purchase in purchases %}
        <tr>
          <td>{{ purchase.dateCreated|date('Y-m-d H:i') }}</td>
          <td>${{ (purchase.amount / 100)|number_format(2) }} {{ purchase.currency|upper }}</td>
          <td class="status-{{ purchase.status }}">{{ purchase.status|title }}</td>
        </tr>
      {% endfor %}
    </tbody>
  </table>
</div>

Conditional Content for Members ​

twig
{# Show different content based on membership status #}
{% if userHasActiveMembership(currentUser) %}
  {# Premium member content #}
  <div class="premium-content">
    <h2>Member Resources</h2>
    <ul>
      <li><a href="/members/downloads">Downloads</a></li>
      <li><a href="/members/webinars">Webinars</a></li>
      <li><a href="/members/forums">Forums</a></li>
    </ul>
  </div>
{% else %}
  {# Free/expired member content #}
  <div class="join-prompt">
    <h2>Become a Member</h2>
    <p>Get access to exclusive resources and benefits.</p>
    <a href="/join" class="button">Join Now</a>
  </div>
{% endif %}

Membership Expiration Notice ​

twig
{% set activeMemberships = getUserActiveMemberships(currentUser) %}
{% if activeMemberships|length > 0 %}
  {% set membership = activeMemberships|first %}
  {% set daysRemaining = ((membership.expiryDate|date('U') - now|date('U')) / 86400)|round %}

  {% if daysRemaining <= 30 %}
    <div class="alert alert-warning">
      <strong>Membership Expiring Soon!</strong>
      Your membership expires in {{ daysRemaining }} days.
      <a href="/renew">Renew Now</a>
    </div>
  {% endif %}
{% endif %}

Chapter-Specific Content ​

twig
{% set activeMemberships = getUserActiveMemberships(currentUser) %}
{% if activeMemberships|length > 0 %}
  {% set membership = activeMemberships|first %}

  {% if membership.chapterId %}
    {% set chapter = craft.app.sites.getSiteByUid(membership.chapterId) %}

    <div class="chapter-content">
      <h2>{{ chapter.name }} Chapter</h2>
      {# Display chapter-specific content #}
      {% set chapterEntries = craft.entries()
        .site(chapter)
        .section('chapterNews')
        .all() %}

      {% for entry in chapterEntries %}
        <article>
          <h3>{{ entry.title }}</h3>
          {{ entry.summary }}
        </article>
      {% endfor %}
    </div>
  {% endif %}
{% endif %}

Multi-Currency Purchase Display ​

twig
{% set summary = getUserPurchaseSummary(currentUser) %}

{% if summary.by_currency|length > 1 %}
  <div class="currency-breakdown">
    <h3>Purchases by Currency</h3>
    {% for currency, data in summary.by_currency %}
      <div class="currency-item">
        <strong>{{ currency|upper }}:</strong>
        {{ data.count }} purchases,
        Total: {{ (data.amount / 100)|number_format(2) }}
      </div>
    {% endfor %}
  </div>
{% endif %}

Purchase Status Breakdown ​

twig
{% set summary = getUserPurchaseSummary(currentUser) %}

<div class="status-breakdown">
  <h3>Purchases by Status</h3>

  {% for status, data in summary.by_status %}
    <div class="status-item status-{{ status }}">
      <span class="status-badge">{{ status|title }}</span>
      <span class="count">{{ data.count }}</span>
      <span class="amount">${{ (data.amount / 100)|number_format(2) }}</span>
    </div>
  {% endfor %}
</div>

Integration with Craft Elements ​

Using with Entry Queries ​

twig
{# Only show entries to active members #}
{% if userHasActiveMembership(currentUser) %}
  {% set entries = craft.entries()
    .section('memberContent')
    .all() %}

  {% for entry in entries %}
    {# Display member-only content #}
  {% endfor %}
{% endif %}

Using in Matrix Fields ​

twig
{# Conditional rendering in Matrix blocks #}
{% for block in entry.contentBuilder.all() %}
  {% if block.type == 'memberOnlySection' %}
    {% if userHasActiveMembership(currentUser) %}
      {{ block.content }}
    {% else %}
      <div class="locked-content">
        <p>This content is only available to members.</p>
      </div>
    {% endif %}
  {% else %}
    {{ block.content }}
  {% endif %}
{% endfor %}

Best Practices ​

1. Cache Results ​

twig
{# Cache membership check for performance #}
{% cache globally for 5 minutes if currentUser %}
  {% set hasActiveMembership = userHasActiveMembership(currentUser) %}
{% endcache %}

{% if hasActiveMembership %}
  {# Member content #}
{% endif %}

2. Null Checks ​

twig
{% if currentUser is not empty %}
  {% set memberships = getUserActiveMemberships(currentUser) %}
  {# Process memberships #}
{% endif %}

3. Error Handling ​

twig
{% set purchases = getUserPurchases(currentUser) %}

{% if purchases|length > 0 %}
  {# Display purchases #}
{% else %}
  <p>No purchases found.</p>
{% endif %}

4. Dev Mode Gating ​

twig
{# Only show debug info in dev mode #}
{% if craft.app.config.general.devMode %}
  {% set debug = debugUserPurchases(currentUser) %}
  {{ dump(debug) }}
{% endif %}

Performance Considerations ​

Query Optimization ​

The Twig functions call the underlying service methods which are optimized, but consider:

  1. Cache frequently accessed data:

    twig
    {% cache globally using key "user-#{currentUser.id}-membership" for 5 minutes %}
      {% set hasActiveMembership = userHasActiveMembership(currentUser) %}
    {% endcache %}
  2. Limit data retrieval:

    twig
    {# Only get what you need #}
    {% set hasActiveMembership = userHasActiveMembership(currentUser) %}
    {# Instead of getting all memberships just to check #}
  3. Avoid in loops:

    twig
    {# Bad - queries for each user #}
    {% for user in craft.users.all() %}
      {% set purchases = getUserPurchases(user) %}
    {% endfor %}
    
    {# Better - pre-fetch or use different approach #}

Security Notes ​

Production Checklist

Before deploying to production:

  1. ✅ Disable debugUserPurchases() function (wrap in dev mode check)
  2. ✅ Verify all user permission checks are in place
  3. ✅ Test with non-admin users
  4. ✅ Clear template caches

ICoTA Members Plugin Documentation