Deleting a Drupal user used to delete their local subscription entities and nothing else: the remote contract (a Stripe subscription, a Recharge contract) or the stored payment instrument (a bank-debit mandate, a vaulted card) stayed live at the payment service and kept billing a customer the site no longer knew (#3616934: Deleting a user deletes local subscription entities but never cancels the remote contract, so billing continues).
What changed for site builders and administrators
Before a deleted user's subscriptions are removed, Subscription Manager now asks each connector to stop billing them remotely — for every subscription of the user, active or not. When the connector confirms, a notice is logged. When it cannot cancel by API, refuses, or fails, a critical log entry and a warning shown to the operator carry every identifier needed to finish the job by hand: connector, remote subscription id, customer id, and instrument id. Deletion is never blocked by a payment service.
The behaviour is on by default and controlled by the new cancel_remote_on_user_delete setting — the checkbox Cancel remote billing when a user account is deleted on the settings form. Existing sites get it switched on by update 10025. Uncheck it only if deleting an account must never affect billing.
Charge records are not deleted with the subscription: they are financial records, and a late-settling payment can still resolve them.
What changed for connector authors
A connector that can stop remote-side billing by API implements the new optional interface:
use Drupal\subscription_manager\Entity\SubscriptionEntityInterface; use Drupal\subscription_manager\RemoteCancellingConnectorInterface; class StripeConnector extends ... implements SubscriptionManagerConnectorInterface, RemoteCancellingConnectorInterface { public function cancelRemoteSubscription(SubscriptionEntityInterface $subscription): bool { return $this->cancelSubscription($subscription->getSubscriptionId()); } }
What "cancel" means depends on the connector's mode. A self-scheduling connector cancels the remote contract immediately — not at period end, since the caller is discarding the local record. A charge-based connector cancels or detaches the stored instrument named by the subscription's instrument_id so it can never be charged again. Implementations must be idempotent: an already-cancelled contract or an already-revoked mandate is a success (return TRUE). Return FALSE when the remote service refused; throwing on transport failure is fine, the caller catches and logs either way.
Connectors that can only cancel through a hosted portal do not implement the interface; the operator gets the critical log line and warning described above. Nothing else about the base SubscriptionManagerConnectorInterface changes.
The shared service
The new subscription_manager.remote_cancellation service (\Drupal\subscription_manager\Service\RemoteCancellation) is the one place that decides how to cancel: the interface when implemented, else the connector's inline executeAction('cancel') when it declares supportsInlineActions(), else "cannot cancel by API". cancel($subscription): bool never throws and logs the outcome with the remote identifiers. Anything that discards a local subscription and needs its remote billing to stop should call it; the Drush subscription_manager:find-duplicates --cancel command now does, replacing a method_exists($connector, 'cancelSubscription') check, so a connector's public cancelSubscription() method is no longer discovered by name.