docs/plans/6.0-payment-gateways-monorepo.md
Status: Design Finalized
Target: Spree 6.0
Depends on: Payment sessions infrastructure (shipped in core), 6.0-multi-vendor-marketplace.md (Phase 3b consumes the Stripe gem)
Author: Damian Legawiec
Last updated: 2026-07-20
Move the payment-session-compatible gateway code from the standalone
spree_stripe and spree_adyen repos into the monorepo as two separate,
optional, lockstep-versioned gems: spree/stripe (gem spree_stripe) and
spree/adyen (gem spree_adyen). Only the v3 payment-session path comes over
— the gateway class, its session/setup-session models and services, payment
sources, and webhook parsing. Everything serving the classic Rails storefront,
the legacy admin, the legacy webhook engines, and the Storefront API v2 stays
behind in the old repos, which become 5.x maintenance branches.
Why: the gateways are the only real implementations of core's payment-session
contract (Spree::PaymentMethod#create_payment_session /
#parse_webhook_event / #complete_payment_session, Spree::PaymentSession
STI), and that contract is still evolving through 6.0. In-repo gateways give
core CI real coverage (today the only in-repo implementation is
Spree::PaymentSessions::Bogus), remove the cross-repo version dance on every
contract change, and put the code where contributors and agents can find it.
The 6.0 marketplace plan independently forces Stripe into the monorepo:
Spree::PayoutProvider::StripeConnect ships OSS in-repo and needs the
stripe SDK, a configured client, and webhook plumbing — keeping the gateway
external would split Stripe integration across two repos.
This amends the 2026-07-15 decision's "likely into spree/core" placement:
dedicated gems, not core.
@vendure/payments-plugin (SDKs as optional peerDependencies), then removed
it in v3.6.0 to a separate community-plugins repo with one package per
gateway — for community ownership and decoupled releases, after the
handler contract had been stable for years. Their situation doesn't
transfer: Spree's gateways are first-party (Vendo-maintained) and our
contract is still moving.packages/modules/providers/payment-stripe → @medusajs/payment-stripe,
hard stripe dependency, framework peer pinned to the exact matching
version), down from five in v1; the rest are community plugins. This — the
strategic gateway in-repo as the contract's reference implementation — is
the model we're following, extended to Adyen.spree_payments) is ruled out: RubyGems has no
optional dependencies, so a single gem must either hard-depend on every
gateway SDK or declare none and fail at runtime. Per-gateway gems with their
own SDK dependency are the Ruby translation of Vendure's optional peer deps.spree/stripe and spree/adyen
directories, publishing spree_stripe and spree_adyen. Core keeps only
the provider-agnostic contract (Spree::PaymentMethod session methods,
Spree::PaymentSession/PaymentSetupSession, Spree::GatewayCustomer,
Spree::Payments::HandleWebhook, the v3 endpoints). No spree_payments
umbrella gem — nothing generic is left for it to own.SpreeStripe::Gateway and
SpreeAdyen::Gateway stay as-is because spree_payment_methods.type and
spree_payment_sessions.type store STI class names — existing
installations upgrade with no data migration and the same Gemfile line.
Core's Spree::Gateway.api_type already maps these namespaces.s.version = Spree.version;
s.add_dependency 'spree', s.version (Rails-style) while the
payment-session contract is hot through 6.0/6.1, to be relaxed to
">= #{s.version}" (the spree_emails pattern) once it stabilizes.
Gateway-only fixes ship as a whole-train patch release.spree gem does not depend on them; host apps
opt in via the Gemfile (starter already lists both). The spree_admin
dependency is dropped — 6.0 gateway configuration goes through the Admin
API / dashboard.stripe, currently
>= 10.1, < 19; adyen-ruby-api-library, currently >= 10.3, < 12).
SDK constraints live in the gateway gemspec, never in core.use_legacy_* toggles are removed. The
6.0 gems ship a single code path. The drop-set (see Design Details) stays
in the old repos.Spree::PayoutProvider::StripeConnect lives in spree/stripe, next to
the gateway and the SDK it requires (marketplace plan Phase 3b).spree/
core/ spree_core
api/ spree_api
emails/ spree_emails (optional, depends on spree)
stripe/ spree_stripe (optional, depends on spree + stripe SDK)
adyen/ spree_adyen (optional, depends on spree + adyen-ruby-api-library)
Each gateway gem is a Rails engine following the existing optional-gem shape:
version from spree/core/lib/spree/core/version.rb, engine initializer
registering the gateway (config.spree.payment_methods << SpreeStripe::Gateway),
migrations installable via spree:install:migrations, its own RSpec suite
(VCR + webmock, as in the source repos).
Both source repos already ship the v3 path alongside the legacy one; the port keeps the v3 half.
Stripe (~37 files, ~1,700 LOC):
SpreeStripe::Gateway + Gateway::PaymentSessions,
Gateway::PaymentSetupSessions, Gateway::PaymentIntents concerns
(intents are Stripe-side API objects wrapped by the session flow — no
intent AR model exists)Spree::PaymentSessions::Stripe, Spree::PaymentSetupSessions::Stripe
(core-table STI subclasses)SpreeStripe::WebhookKey + PaymentMethodsWebhookKey models and their
migration (spree_stripe_webhook_keys, join table) — encrypted signing
secrets.stripe scopes on
PaymentMethod/GatewayCustomerAdyen (~98 files, ~3,400 LOC, of which 60 are one-per-method payment sources):
SpreeAdyen::Gateway + session/setup-session concerns, HMAC-validating
parse_webhook_event, management-API auto-configuration (webhook setup,
HMAC/client key generation, allowed origins)Spree::PaymentSessions::Adyen, Spree::PaymentSetupSessions::Adyen
(core-table STI subclasses)Spree::Metafields on the payment.app/javascript/, storefront ERB partials,
checkout/store controller decorators and helpers, redirect/confirm-payment
controllers, Apple Pay domain-verification file routesstripe_event engine mount + subscriber block and
its handler services; Adyen's POST /adyen/webhooks controller and the
config.spree_adyen event-code → handler registrySpreeAdyen::PaymentSession standalone model and its
spree_adyen_payment_sessions table + migrations (superseded by the
core-table Spree::PaymentSessions::Adyen)lib/spree_api_v2/) and conditional routesuse_legacy_* preferences and both branches of their conditionalsThe Stripe Tax calculator (SpreeStripe::Calculators::StripeTax) is ported
only if it fits the 6.0-tax-provider.md direction (see Open Questions).
Nothing new. Gateways register on Rails.application.config.spree.payment_methods
in their engine initializer; GET /api/v3/admin/payment_methods/types derives
availability from loaded providers, so "gem in Gemfile" = "type offered in
admin" with no conditional-SDK logic. Webhook endpoints are the core v3 ones
(/api/v3/webhooks/payments/:payment_method_id); Gateway#webhook_url and
#parse_webhook_event already target them.
spree/stripe (smaller keep-set; sequenced first
because marketplace Phase 3b builds PayoutProvider::StripeConnect on
it). Bring the spec suite + cassettes; wire the gem into monorepo CI and
the release train; add it to the starter/dev-server path mounts.spree/adyen the same way.Spree::PayoutProvider::StripeConnect
inside spree/stripe.gem 'spree_stripe'
/ gem 'spree_adyen' lines, now resolved from the 6.0 release train. No
STI data migration. Apps still on the legacy storefront/webhook paths must
complete the v3 migration (the use_legacy_* off state) before 6.0, per
the gems' existing 5.x migration guides.spree_stripe/spree_adyen repos
— critical fixes only. New gateway capability lands in the monorepo port.SpreeStripe/SpreeAdyen namespaces anywhere (docs,
examples, seeds): STI columns depend on them (Key Decision 2).Spree::PaymentMethod session methods, Spree::PaymentSession,
Spree::Payments::HandleWebhook, the v3 session/webhook endpoints) must be
checked against both gateway implementations — after the port, monorepo CI
does this; until then, coordinate with the external repos before merging.spree_paypal_checkout) follow this plan's
shape: own spree/<provider> gem, existing name kept if published, lockstep
version, exact spree pin, SDK as the gem's own dependency.spree meta gem must not gain a dependency on any gateway gem.spree/stripe, or drop and revisit as
a TaxProvider under 6.0-tax-provider.md? Leaning drop-and-revisit.spree_paypal_checkout timing: same treatment, but in-scope for 6.0 or
after? Not blocking this plan.docs/plans/decisions.md — 2026-07-20 entry (this plan; amends 2026-07-15
"likely into spree/core")docs/plans/6.0-multi-vendor-marketplace.md — Phase 3b (StripeConnect
payout provider), OSS/Enterprise boundarydocs/plans/6.0-tax-provider.md — Stripe Tax open questionspree/spree_stripe, spree/spree_adyenvendurehq/community-plugins (v3.6.0 removal
of packages/payments-plugin)medusajs/medusa → packages/modules/providers/payment-stripe
(@medusajs/payment-stripe)