Skip to content

ADR 0001 — License architecture

Status: accepted Date: 2026-08-21

Context

AD Form is the first product on a platform that will host several. The plugin runs on infrastructure the customer controls and must determine what the site is allowed to do.

The obvious shortcuts are all wrong. Contacting Stripe from the plugin would put a payment secret on customer infrastructure. Querying the dashboard database would couple every site to our schema. Computing a plan locally from prices would make a price change a plugin release.

The audited plugin had no outbound HTTP, no cron, no transients and no crypto, so none of this could be retrofitted onto existing plumbing.

Decision

The platform API is the single authority. The plugin sends signals and consumes signed answers.

  • All communication goes through one LicenseApiClient over one ApiTransportInterface, implemented by WpHttpTransport using wp_remote_post().
  • Base URL, API version, product id and routes live only in LicenseConfig.
  • Three endpoints: activate, validate, deactivate.
  • The plugin never contacts Stripe, never reads a price, never computes a plan and never enforces its own activation limit as the authority.
  • The response carries plan, license_status and entitlements. The plugin works with those, never with Stripe price ids.

LicenseManager is a façade with no logic of its own; the work sits in small single-purpose services.

Consequences

Good:

  • Pricing, plan composition and activation limits change server-side with no plugin release.
  • One place to audit for outbound traffic, and one seam to stub in tests.
  • Adding a second product to the platform needs no change here.
  • The plugin ships no secret worth stealing.

Costs:

  • The plugin cannot answer any licensing question offline on first use. Handled by ADR 0003.
  • A malformed or hostile API response is now an input class that must be handled everywhere. Handled by ADR 0002.
  • Licensing introduces the plugin's first cron consumer and first HTTP client, which is new operational surface.

Rejected

Local price tables. Every price change becomes a release, and old installations disagree with the dashboard.

Direct Stripe calls. Requires a Stripe secret on customer infrastructure. Non-negotiable.

Dashboard database access. Couples every site to our schema and needs credentials on customer infrastructure.

A generic HTTP helper reused across the plugin. Scattered wp_remote_post() calls make the outbound surface unauditable.

Digitale Lösungen. Persönlich. Zukunftssicher.