How to revoke a license role upon refund

Created on 9 September 2025, 6 days ago

I spent the better part of a day trying to figure out how to automatically remove a role and revoke a license after issuing a refund. I finally cobbled together a working solution, so I thought I’d share it in case someone ever needs to do something similar. As always, there are usually several ways to accomplish a task, some more efficient than others. I suppose the same result could also be achieved with Rules. Feel free to chime in if you know of a better approach.

Anyway, the task can be accomplishment with an EventSubcriber and services.yml file. Change the constant values to suit your particular use case.

custom.services.yml

services:
  custom.payment_subscriber:
    class: Drupal\custom\EventSubscriber\PaymentRefundSubscriber
    tags:
      - { name: event_subscriber }

/src/EventSubscriber/PaymentRefundSubscriber.php

<?php

namespace Drupal\custom\EventSubscriber;

use Drupal\state_machine\Event\WorkflowTransitionEvent;
use Drupal\commerce_payment\Entity\PaymentInterface;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_license\Entity\LicenseInterface;
use Drupal\user\UserInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Reacts to payment refunds: removes a specific user role and revokes licenses for a specific product type.
 */
class PaymentRefundSubscriber implements EventSubscriberInterface {

  /**
   * The user role to remove.
   */
  const USER_ROLE = 'teacher';

  /**
   * The product type to check for licenses.
   */
  const PRODUCT_TYPE = 'access_plan';

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      'commerce_payment.post_transition' => 'onPaymentPostTransition',
    ];
  }

  /**
   * Removes the user role defined in USER_ROLE and revokes licenses
   * for products defined in PRODUCT_TYPE when a payment is refunded.
   *
   * @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
   *   The workflow transition event for the payment.
   */
  public function onPaymentPostTransition(WorkflowTransitionEvent $event) {
    $payment = $event->getEntity();
    if (!$payment instanceof PaymentInterface) {
      return;
    }

    $transition = $event->getTransition();
    if ($transition->getToState()->getId() !== 'refunded') {
      return;
    }

    $order = $payment->getOrder();
    if (!$order instanceof OrderInterface) {
      return;
    }

    $user = $order->getCustomer();
    if ($user instanceof UserInterface && $user->hasRole(self::USER_ROLE)) {
      // Remove the user role defined in USER_ROLE.
      $user->removeRole(self::USER_ROLE);
      $user->save();
    }

    // Revoke any active licenses linked to the order items for the product type defined in PRODUCT_TYPE.
    foreach ($order->getItems() as $order_item) {
      /** @var LicenseInterface $license */
      $license = $order_item->get('license')->entity;
      if (!$license instanceof LicenseInterface) {
        continue;
      }

      $purchased_entity = $order_item->getPurchasedEntity();
      if (!$purchased_entity || $purchased_entity->bundle() !== self::PRODUCT_TYPE) {
        continue;
      }

      // Revoke the license if allowed.
      if ($license->getState()->isTransitionAllowed('revoke')) {
        $license->getState()->applyTransitionById('revoke');
        $license->save();
      }
    }
  }
}
πŸ’¬ Support request
Status

Active

Version

3.0

Component

Documentation

Created by

πŸ‡ΊπŸ‡ΈUnited States johnhanley

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024