Change record status: 
Introduced in branch: 
1.0.x
Introduced in version: 
1.0.0-beta7
Description: 

Subscription Manager now has its own manage-subscription page for connectors whose billing service has no customer portal, and charge-based subscriptions can be cancelled and resumed (#3627571: Build the membership portal page on capability interfaces, replacing the unused inline-actions API). The unused "inline actions" API on the connector interface is removed in favour of capability interfaces. Every connector needs a small change.

Connector authors: what breaks

  • redirectToPortal() is no longer part of SubscriptionManagerConnectorInterface. A connector with a hosted portal keeps the method and adds the new interface to its class declaration:
    use Drupal\subscription_manager\HostedPortalConnectorInterface;
    
    class StripeConnector extends ... implements SubscriptionManagerConnectorInterface, HostedPortalConnectorInterface {
    

    Without it, the manage route and api/portal-url stop redirecting to the connector's portal and show Subscription Manager's own page instead. A connector with no portal (typically a charge-based one) simply does not implement it and can delete any stub.

  • supportsInlineActions(), getSubscriptionDisplayData(), getAvailableActions() and executeAction() are removed from the interface. Nothing ever called them. Delete them, or keep them as ordinary methods if your own code uses them.
  • RemoteCancellation no longer falls back to executeAction('cancel'). A connector that can cancel by API implements RemoteCancellingConnectorInterface (#3616934: Deleting a user deletes local subscription entities but never cancels the remote contract, so billing continues); otherwise the operator gets the "cannot cancel by API" log entry.

Connector authors: what a charge-based connector gains

The native page offers each action only when the connector supports it:

  • Cancel subscription / Keep my subscription — always, for charge-based subscriptions; no connector code needed.
  • Update payment method — when the connector implements InstrumentReplacingConnectorInterface. This is where the dunning mails send a customer whose payment failed.
  • Instrument revocation when a subscription ends — when the connector implements RemoteCancellingConnectorInterface: cancel or detach the mandate or vaulted card named by instrument_id.

Cancellation semantics

$engine = \Drupal::service('subscription_manager.cycle_engine');
$engine->cancel($subscription);        // At the end of the paid period.
$engine->cancel($subscription, TRUE);  // Now.
$engine->resume($subscription);        // Withdraw a pending cancellation.
  • By default a cancellation takes effect at the end of the period already paid for: cancel_date is set, expiry becomes the cycle anchor, access and roles continue until then, the engine stops billing, and a scheduled downgrade is dropped. No refund or proration.
  • Until the expiry it can be resumed; the stored instrument is untouched.
  • At the expiry the cron sweep ends the subscription: it is deactivated (roles revoked), taken out of dunning, and its instrument revoked.
  • Cancelling a past-due subscription ends it immediately — the current period is the unpaid one — and stops the retries. A charge that fails while a cancellation is pending also ends the subscription rather than starting dunning.
  • Plan changes are refused while a cancellation is pending.
  • cancel() throws for a subscription that is not charge-based: a remote contract is cancelled at its own service.

New on the subscription entity: getCancelDate(), getExpiry(), hasPendingCancellation(), isChargeBased(); willRenew() now returns FALSE for a pending cancellation and TRUE for an active charge-based subscription. Two events, both carrying a SubscriptionCancellationEvent: CancellationEvents::SCHEDULED and CancellationEvents::REVERTED. There are no built-in cancellation mails; subscribe to the events to send one.

Switching connectors

The subscribe route used to send anyone who had a subscription to their current connector. Now:

  • /subscription-manager/subscribe?connector=ID is honoured for existing subscribers too, so a switch to another connector (a free rail to a paid one, say) can be started.
  • ?change=1 shows the chooser to an active subscriber, with their current connector marked (current on each item of the chooser template and of the api/subscribe-url response).
  • A user whose only subscription has lapsed gets the chooser like a new subscriber.

A cross-connector switch is immediate: saving the new subscription ends the old one through the single-active rule. For a charge-based old subscription that now also clears dunning and revokes its instrument, unless the new subscription charges that same instrument on the same connector.

Site builders and themers

  • New template subscription-manager-portal.html.twig (variables state, notice, rows, actions); the wrapper carries a subscription-manager-portal--{state} class for active, cancelling, past_due and ended.
  • New routes under /user/{user}/manage-subscription/: cancel, resume and payment-method, with the same access as the manage route (the owner with manage own subscriptions, or an administrator).
  • The manage_route setting still lets a site replace the page with its own.
  • The chooser template gains a "Your current plan" badge for item.current.

No update hook is needed: no schema or configuration changes.

Impacts: 
Site builders, administrators, editors
Module developers
Themers