- Issue created by @John_B
- 🇳🇿New Zealand john pitcairn
Shouldn't a fair bit of this be the responsibility of commerce_recurring?
- Status changed to Closed: works as designed
10 months ago 5:41pm 6 June 2024 - 🇬🇧United Kingdom jonathanshaw Stroud, UK
I'm not sure I believe point 7.
I don't see how Stripe takes payment just because a customer card is updated on Drupal.
If commerce_recurring tries to process a payment for an order for a cancelled subscription, that's an issue with commerce_recurring not commerce_stripe. If I remember right, by default commerce_recurring does exactly this, because technically the order has already fallen due and some sites might want to collect the money in this case; canncelling the sub only spares the customer from accruing future liabilities, not paying existing liabilities.
I use this code to deal with this issue in my project:
<?php namespace Drupal\my_project\EventSubscriber; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_recurring\Event\SubscriptionEvent; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\state_machine\Event\WorkflowTransitionEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Class RecurringSubscriptionChangeSubscriber. Provides subscriber for orders. */ class RecurringSubscriptionChangeSubscriber implements EventSubscriberInterface { /** * The order storage. * * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $orderStorage; /** * The log storage. * * @var \Drupal\commerce_log\LogStorageInterface */ protected $logStorage; /** * Constructs a new PaymentEventSubscriber object. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException */ public function __construct(EntityTypeManagerInterface $entity_type_manager) { $this->orderStorage = $entity_type_manager->getStorage('commerce_order'); $this->logStorage = $entity_type_manager->getStorage('commerce_log'); } /** * {@inheritdoc} */ public static function getSubscribedEvents() { $events['commerce_subscription.cancel.post_transition'] = 'onCancel'; $events['commerce_recurring.commerce_subscription.update'] = 'onUpdate'; return $events; } /** * Cancel in-progress orders when a subscription is cancelled. * * OOTB commerce_recurring cancels drafts but not placed orders. * * @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event * The event. */ public function onCancel(WorkflowTransitionEvent $event) { $subscription = $event->getEntity(); $order_ids = $subscription->getOrderIds(); if (empty($order_ids)) { return; } // Load outstanding orders and cancel them. $order_ids = $this->orderStorage->getQuery() ->condition('type', 'recurring') ->condition('order_id', $order_ids, 'IN') ->condition('state', 'needs_payment') ->accessCheck(FALSE) ->execute(); $orders = $this->orderStorage->loadMultiple($order_ids); foreach ($orders as $order) { $order->getState()->applyTransitionById('cancel'); ; $order->save(); $this->logStorage->generate($order, 'my_project_order_subscription_canceled', [])->save(); } } /** * Update orders when a subscription is updated. * * OOTB commerce_recurring makes limited changes to draft but * not placed orders. For the limited aspect see * https://www.drupal.org/project/commerce_recurring/issues/3200723 * Not touching placed orders is by design; the subscription scheduled * changes field is designed to respect the fact that typically * subscribers cannot change a bill once it's fallen due. * * @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event * The event. */ public function onUpdate(SubscriptionEvent $event) { $subscription = $event->getSubscription(); $order_ids = $subscription->getOrderIds(); if (empty($order_ids)) { return; } // Load outstanding orders and refresh them. $order_ids = $this->orderStorage->getQuery() ->condition('type', 'recurring') ->condition('order_id', $order_ids, 'IN') ->condition('state', ['draft', 'needs_payment'], 'IN') ->accessCheck(FALSE) ->execute(); $orders = $this->orderStorage->loadMultiple($order_ids); foreach ($orders as $order) { $order->setRefreshState(OrderInterface::REFRESH_ON_SAVE); $order->save(); } } }
- 🇬🇧United Kingdom John_B London (UK), Worthing (UK), Innsbruck (Tirol)
@jonathanshaw Thanks for sharing your comment and code!