Subscription-first commerce for physical goods, on code you own
The subscription layer in the open: renewals, dunning, payment recovery, self-service. Not a Shopify add-on. Not a billing API.
npm install --save-exact @openlup/core@0.37.0The subscription layer in the open: renewals, dunning, payment recovery, self-service. Not a Shopify add-on. Not a billing API.
npm install --save-exact @openlup/core@0.37.0TypeScript · Node.js 24 · PostgreSQL 16 · Vercel · Supabase · OCI image
Providers sit behind ports. Ownership and extension
Subscriptions are the business, not an add-on. OpenLup keeps them in the open core, with no app fee and no revenue share.
The core knows neither your brand nor your country.
227 pgTAP files · 2 389 test files · 113 platform migrations · measured on npm 0.37.0
A neutral store starter to begin from; kernels for bundles, inventory, pricing, promotions, shipping, risk and company identity; and example adapters for payments, fulfilment and e-mail.
| Open core | Yours |
|---|---|
| Checkout to renewal, the subscription path | Brand look and identity |
| Subscriptions, dunning, mandates, recovery | Copy, tone, configurator logic |
| Messaging with schedule and observability | Taxes, carriers, fiscal rules |
| Customer account and self-service actions | ERP, WMS, CRM integrations |
| Operator panel | Pricing and promotion rules |
| Payments, fulfilment, invoicing behind ports | Hosting choice |
| Runtime, migrations, scheduler, storage |
Nothing is withheld. Limits are documented.
Subscriptions, dunning, mandates and recovery are open core and will not become paid tiers.
export type SubscriptionEngineErrorCode =
| "invalid_timestamp"
| "invalid_template"
| "inactive_subscription"
| "edit_window_closed"
| "line_not_found"
| "duplicate_line"
| "invalid_slide_target"
| "invalid_transition"
| "terminal_cycle"
| "missing_payment_method"
| "dunning_not_exhausted";Refusals are values. Nothing throws.
A late payment moves the next cycle later by the days it was late. Never earlier.
const shiftedByDays = Math.max(0, Math.floor(
(Math.min(paidAt.getTime(), recordedAt.getTime()) - Date.parse(input.cycle.scheduledAt)) / DAY_MS,
));
const nextCycleAt = new Date(Math.max(
Date.parse(input.subscription.nextCycleAt) || 0,
Date.parse(onTimeNextCycleAt) + shiftedByDays * DAY_MS,
)).toJSON();From subscriptionEnginePayment.ts, npm 0.37.0.
Three rungs, then automatic retries stop. The subscription stays active and repairable.
if (ladderTerminatedByClass(failureClass, cadence)) return null;
const hours = cadence.backoffHours[attemptNumber - 1];
// Covers both ends at once: an attempt below 1 and an attempt past the last
// ladder slot are equally "no next charge is due".
if (attemptNumber < 1 || hours === undefined) return null;From cycleHardening.ts, npm 0.37.0.
Lifecycle e-mail goes through a durable outbox, and a dropped webhook is reconciled by polling.
Failure paths are pinned by tests: 227 pgTAP files and 2 389 test files at npm 0.37.0.
The customer keeps the cadence they paid for, whatever the payment did.
The next cycle is the cycle’s own scheduled date plus its cadence plus the days the payment was late, and it never moves earlier. The clamp means a replayed payment webhook cannot pull a charge forward into a window the customer already paid for.
With delivery alignment on, the next cycle starts from the latest confirmed delivery plus the cadence, and renewals wait while a parcel’s outcome is unknown, so parcels never stack up. The mode is off by default.
E-mail is the one channel today.
| Adapter | On npm | Lane | Proof scope |
|---|---|---|---|
| Supabase PostgreSQL (managed database) | yes | managed | Data gateway and job ledger for the managed rail. Fresh-install, upgrade and existing-store restore proofs for this rail remain partial. |
| Supabase Auth | yes | managed | Customer and operator sign-in on the managed rail. Operator sign-in smoke checks and a hosted settings readback remain open. |
| Supabase Storage | yes | managed | Blob storage binding. Serving and access proofs remain open. |
| Portable PostgreSQL (self-host and Amazon RDS recipes) | yes | portable | Local self-host recipe receipt, local store transfer and restore witnesses, and one hosted RDS PostgreSQL 16 recipe run on a single synthetic instance. No hosted transfer or recovery has run, and none of this is production qualification. |
| Neon | no | portable | No receipt yet. A Neon recipe receipt and Neon to Amazon RDS transfer and recovery are planned in the hostability round. |
| Generic OIDC and JWKS identity | yes | portable | Opt-in access-token verifier. A real-issuer lifecycle witness passed on one disposable hosted origin; no production qualification. |
| S3-compatible object storage | yes | portable and managed | Local recipe with two gateway accounts and one hosted run on a single synthetic bucket in one region. No production qualification. |
| Vercel (Node functions, scheduled jobs, Blob) | yes | managed | Blob binding and the reference default bundle with the managed database, without the Edge runtime. A Vercel host for the portable neutral store is planned in the hostability round. |
| Generic Node host and OCI image | yes | portable | Local self-host receipt and one hosted run on a single synthetic VM with restart, drain and A to B to A rollback checks. No production qualification. |
| Netlify (independent PaaS candidate) | no | portable | Candidate only, not yet measured. It is evaluated in the hostability round. |
| Resend email | yes | portable and managed | Opt-in adapter on the communications entry with offline conformance. No provider-account evidence. |
| Klaviyo marketing sync | yes | portable and managed | Opt-in adapter with offline conformance only; account verification is pending. |
| Stripe payments | yes | managed | Opt-in example adapter with an injected client. Refund creation and split authorization and capture are not available, some hosted paths are not observed, and real money runs only on the managed rail. |
| Tpay payments | yes | managed | Opt-in example adapter with simulator evidence only. Real settlement and provider readback are not verified. |
| OmniPack warehouse fulfillment | yes | managed | Opt-in, default-off example with offline conformance. Exact absence, label create and void and cancellation are not available, and some dispatch and reconciliation paths have not been exercised. |
| Polish company registries (VAT whitelist, GUS/CEIDG) | no | portable and managed | Not packaged. A packaged example lookup adapter comes in the A5 adapter round. |
| External sales channels (normalized import) | yes | portable and managed | Normalized ingest contracts and bindings. No real connector ships, and conversion to a physical order refuses on the portable rail. |
Proof scope as the OpenLup roadmap states it; passing conformance does not qualify a provider.
Three runtime bundles: Vercel with Supabase, Node with Supabase, Node with plain PostgreSQL. None is a supported production recipe yet. Real money runs on the managed rail today; a portable real-money rail is on the roadmap. No hosted service exists.
Agents work within explicit human authority. A human is answerable for every line.
The properties that make a codebase legible to an agent.
OpenLup is built with Claude Code and Codex: they implement, verify and review, and every change stays within the maintainer’s grant.
| Product | Kind | Subscriptions, dunning, self-service | Published fees | Read |
|---|---|---|---|---|
| Saleor | open-source platform, BSD-3-Clause | no subscription engine; recurring payments built on the Transaction API | none; self-hosted | 2026-11-06 |
| WooCommerce Subscriptions | paid extension for WooCommerce, by Woo | subscriptions; retries off by default; self-service in My Account | $279 per year | 2026-11-06 |
| Medusa | open-source platform, MIT | not in core; a documented recipe | none; self-hosted | 2026-11-06 |
| Sylius | open-source platform, MIT; paid Plus module | subscriptions in Sylius Plus (0.x); dunning not documented | Sylius Plus from €800 per year | 2026-11-06 |
| Shopware | MIT Community; commercial plans | Beyond plan subscriptions; pause, cancel; dunning not documented | Beyond, custom price | 2026-11-06 |
| Stripe Billing | billing engine, SaaS; no storefront or fulfilment | subscriptions; Smart Retries; customer portal | 0.7% of billing volume or from $620/month (US) | 2026-11-06 |
| Shopify Subscriptions | Shopify app | subscriptions; configurable retries; customer self-service | free app on a Shopify plan from $39/month (US) | 2026-11-06 |
| Recharge | Shopify app | subscriptions; dunning in every plan; customer portal | from $149/month + 1.50% + 20¢ per order | 2026-11-06 |
| Loop | Shopify app | subscriptions; 15 retries on Starter; customer portal | free plan; from $99/month + 1.0% per order | 2026-11-06 |
| JUO | Shopify app | subscriptions; dunning not documented | from $199/month + 1.25% + 19¢ per order | 2026-11-06 |
| OpenLup | open-source platform, Apache-2.0 | subscriptions, dunning and self-service in the open core | none per order; self-hosted | policy |
Feature presence and published fees only, read on each product’s own pages on the date shown; card processing is extra everywhere. One deployment serves one store today.
Anything published under Apache-2.0 stays open. Permanently. Open never becomes paid. In the operational layer, never in withheld code. There is no CLA, and there will not be one. AI-assisted contributions are welcome. They are not banned, and they will not be banned. Nothing in this project carries a service-level agreement. The first public OpenLup root is a mechanically derived, fresh-history subset of the pre-split development repository, not a hand-assembled copy.
Commerce is strictly additive: paid work is built on top of a stabilized open core, never carved out of it. There are no tiers. Everything the project publishes is open.
Not designed in the abstract. Extracted from the author’s own shop, velipet.com, which runs it in production with real customers and real money.
Measured on npm 0.37.0. velipet.com is the author’s shop, not an independent adopter; it adopts the published packages, and its changes go through OpenLup’s own review and releases.
Subscriptions are the business. They should not be a monthly app fee and a revenue share. I built OpenLup so a brand can own them.
To evaluate a whole store, yes: the neutral starter installs from npm on PostgreSQL, with sandbox payments. Real money runs on the managed rail, and nothing is stable, supported or certified yet.
Everything published under Apache-2.0, permanently. Subscriptions, renewals, dunning, mandates, recovery and messaging will not become paid tiers.
No tiers today. Paid work, if it comes, is strictly additive: built on top of a stabilized open core, never carved out of it.
Brand, copy, storefront, catalogue, your market's taxes and carriers, your ERP and CRM integrations, your pricing rules, your hosting.
None are certified. Example adapters exist for payments, fulfilment, e-mail, auth and storage, each with its proof scope written down.
One deployment serves one store today.
velipet.com, the author’s own shop. It is not an independent adopter.
Yes. Disclose it and stay accountable for every line. An agent opens a pull request only with a maintainer’s explicit authority.
Yes. Apache-2.0 and a fresh-history root. The name and logo are not part of the licence, so a fork takes its own name.