Skip to content

[Provider plugins S1] Stripe reference provider and starter migration #71

Description

@AntoineToussaint

Outcome

Create the Stripe reference provider, migrate SaaS Starter billing setup from
provider-specific shell logic to Codefly's provider lifecycle, and prove the
first complete local-dogfood vertical slice.

Stripe establishes the implementation baseline that opens Sentry and Resend
for parallel work. It does not declare the protocol fully stable; final
stability requires all three reference providers.

Dependencies

Foundation conformance gate:

Transitive foundation:

Do not implement Stripe-specific branches in Core or CLI. A generic defect
found by this issue is fixed behind the provider-neutral contract and added to
F4 conformance.

Repository ownership

This bootstrap issue is tracked in codefly-dev/module-saas-starter because
the final provider repository does not yet exist.

Deliverables:

  1. create codefly-dev/provider-stripe;
  2. implement provider code/tests/docs there;
  3. update SaaS Starter generic billing configuration, dogfood, and setup shim;
  4. make only provider-neutral bug fixes in Core/CLI with cross-linked PRs.

Operational prerequisites

Start immediately; do not create separate issues:

  • dedicated Stripe sandbox/test account isolated from personal/production
    data;
  • management and runtime credential strategy;
  • opt-in CI/live-acceptance secret channel;
  • deterministic ownership prefix/metadata;
  • cleanup automation;
  • Stripe CLI installed only for explicit local-forwarded acceptance;
  • documented operator able to reveal/rotate a webhook secret in Workbench for
    manual recovery testing.

No default CI depends on live Stripe availability.

Provider scope

Inputs

Define typed inputs:

  • API base selected through host origin admission;
  • explicit Stripe API version;
  • management credential handle;
  • runtime credential handle or explicit local shared-key opt-in;
  • account policy (sandbox/test, never inferred solely by provider);
  • callback endpoint reference and exposure mode;
  • webhook lifecycle;
  • desired event set;
  • existing webhook ID/import identity;
  • existing webhook-secret reference where applicable;
  • local-forwarder secret input;
  • description/ownership metadata policy.

Secrets are references/handles, never literals or provider RPC bytes.

Credential and mode boundary

Stripe Account does not expose livemode. The host must:

  • classify sk_test_, rk_test_, sk_live_, rk_live_ while resolving the
    credential;
  • bind mode into credential handle, plan, authorization, and receipt;
  • reject local live credentials before a mutation;
  • cross-check livemode on brokered Stripe resource responses where present;
  • turn a host classification/response mismatch into blocked/uncertain safety
    failure.

The provider receives only the host-attested mode.

Credential roles:

  • management: account read + webhook management;
  • runtime: only product billing operations required by SaaS Starter;
  • webhook verification: whsec_;
  • browser: none for hosted Checkout v0;
  • build: none.

Restricted-key scope categories are Dashboard-defined and not fully enumerated
in public docs. Qualify management/runtime restricted keys live rather than
claiming documentation proof.

An explicit shared management/runtime key is allowed only for local dogfood:

  • warning in plan;
  • dedicated policy opt-in;
  • receipt caveat;
  • never default production behavior.

Observed resources

stripe.account:

  • safe account ID/display subset;
  • charges/details readiness where relevant;
  • no false claim that Account contains livemode;
  • host-attested mode.

stripe.webhook-endpoint:

  • complete list/retrieve;
  • exact ID;
  • URL;
  • enabled event set;
  • endpoint API version;
  • status/disabled;
  • Codefly-owned metadata/description;
  • safe revision/created fields;
  • livemode;
  • secret presence/reference only.

Ignore unrelated remote/account/customer fields.

Field ownership and drift

Codefly owns:

  • callback URL derived from Codefly endpoint/ingress;
  • enabled event set, compared as a sorted set with Stripe wildcard semantics;
  • endpoint API version;
  • Codefly-namespaced metadata;
  • Codefly description marker;
  • active/disabled state only where explicitly configured.

Stripe owns:

  • endpoint ID/created/application;
  • webhook signing secret bytes;
  • unrelated metadata.

Changing endpoint API version is REPLACE, not UPDATE: Stripe does not expose
that field in update parameters. The plan must show:

  • destructive/replacement semantics;
  • new signing-secret effect;
  • old endpoint retention/delete ordering;
  • approval and rollback/manual recovery.

Remote manual changes to Codefly-owned fields produce drift. Foreign metadata
is ignored.

Ownership, discovery, and migration

On create, stamp supported Stripe metadata with safe Codefly identity:

  • workspace identity;
  • environment/binding;
  • resource address;
  • operation/prospective ID.

Never auto-adopt by URL.

If same-URL endpoints exist:

  • show candidates with exact ID, created time, events, version, status;
  • require operator selection/import by exact ID;
  • multiple matches are an unmanaged conflict;
  • no delete/update until imported/owned.

Stripe limits webhook endpoints per account; map quota failure and surface
cleanup/remediation without deleting unmanaged endpoints.

Managed lifecycle

Support:

  • Observe;
  • CREATE;
  • UPDATE allowed mutable fields;
  • REPLACE for API-version/write-once-secret changes;
  • exact-ID IMPORT;
  • exact-owned DELETE only through explicit destroy;
  • NOOP on repeated desired state;
  • MANUAL_ACTION for Dashboard secret reveal/rotation where appropriate.

Deletion defaults retain.

Idempotency and uncertain outcomes

Before POST:

  • host persists pre-send action/request/prospective ID/idempotency checkpoint;
  • broker injects stable key;
  • provider maps safe Stripe request/error/rate metadata.

Same-attempt replay is allowed only:

  • with exact same request/key;
  • within a conservatively qualified Stripe retention window;
  • after fresh authorization;
  • through host coordinator retry.

Live acceptance must verify Stripe replays the original webhook-create body,
including broker capture of the same secret, without duplicate endpoint.

Beyond the qualified replay window:

  • do not blindly POST;
  • observe metadata-stamped endpoints;
  • converge/import a unique match;
  • use Workbench reveal/rotate MANUAL_ACTION;
  • or require approved replace.

Response filtering and signing-secret capture

Manifest declares:

  • safe fields;
  • webhook create secret as CAPTURE_TO_SINK;
  • every other secret-shaped Stripe response field suppressed/captured;
  • no raw response passthrough.

The signing secret:

  • goes broker -> durable local sink;
  • reaches provider only as opaque reference/presence/fingerprint/result;
  • never appears in provider memory, state, plan, logs, diagnostics, receipts,
    cassettes, Git, or command arguments;
  • survives later projection failure.

Lost-response replay must capture/recover without duplicate.

Callback modes

local-forwarded:

  • resolve actual Codefly callback origin/port at runtime;
  • provide exact loopback target to operator/companion;
  • Stripe CLI listen --forward-to;
  • CLI-listen whsec_ is distinct from registered endpoint secret and enters
    through safe host input;
  • do not create a remote endpoint;
  • diagnose expired Stripe CLI login and service-not-ready delivery;
  • no hardcoded port.

public:

  • require stable public HTTPS origin from Codefly resolver;
  • re-resolve at Apply;
  • manage remote webhook;
  • origin change invalidates plan.

existing:

  • exact endpoint ID + supplied/recoverable secret reference;
  • observe until explicitly imported.

billing@1 projection

Public:

  • BILLING_PROVIDER=stripe
  • admitted API base
  • pinned Stripe API version

Secret/reference:

  • runtime Stripe key/reference;
  • webhook verification secret/reference.

Management key is never projected by default. Contract validation enforces
purpose.

Errors and rate limits

Map:

  • authentication;
  • permission/scope;
  • wrong mode;
  • not found;
  • ambiguous;
  • endpoint quota;
  • parameter/idempotency mismatch;
  • rate limit/retry-after;
  • timeout before send;
  • sent outcome unknown;
  • permanent validation.

No raw Stripe error body is surfaced.

SaaS Starter migration

Replace scripts/setup/stripe.sh with a non-writing compatibility shim after
plugin parity.

Safe legacy inputs may translate to Codefly input sources:

  • API-key file/reference;
  • env input file;
  • endpoint/binding selection.

Flags without equivalent semantics hard-fail with exact guidance:

  • --provision-webhook;
  • --skip-remote-validation;
  • --force;
  • copied webhook origin/port.

The shim must:

  • accept rk_test_ as well as sk_test_ through host classification;
  • never perform curl, write configuration, or manage remote resources;
  • never silently share management/runtime credentials;
  • explain exact import-by-ID for script-created endpoints;
  • preserve shared setup helpers for non-migrated provider scripts;
  • be statically tested as a thin shim.

No URL-only adoption from the current script is retained.

Required tests

Tier 0

  • schema/input/purpose;
  • host mode classification inputs/results;
  • field ownership and sorted event comparison;
  • CREATE/UPDATE/REPLACE/NOOP/IMPORT/DELETE plans;
  • API-version change -> REPLACE/new secret effect;
  • URL conflict -> unmanaged/manual;
  • exact ownership/delete;
  • error/rate/quota mapping;
  • output contract and management/runtime separation;
  • shim flag mapping/hard failures.

Tier 1 cassettes

  • account;
  • paginated/list/retrieve endpoints;
  • create/update/delete;
  • idempotent replay;
  • secret capture;
  • auth/scope/mode errors;
  • 429;
  • quota;
  • malformed/extra secret response;
  • filtered safe output only.

Tier 3 live sandbox

Using dedicated account:

  1. qualify restricted management/runtime key permissions;
  2. account observe and host mode classification;
  3. create endpoint -> capture secret;
  4. rerun -> NOOP;
  5. update event set -> converge -> NOOP;
  6. change API version -> REPLACE -> new secret reference/fingerprint;
  7. same-key lost-response replay -> no duplicate/same capture;
  8. expired replay window policy -> no blind create;
  9. ownership metadata round-trip;
  10. same-URL unmanaged conflict -> exact import;
  11. local live-key denial;
  12. response livemode mismatch handling;
  13. local-forwarded mode creates no permanent endpoint;
  14. explicit destroy deletes only owned test endpoint;
  15. cleanup leaves account at baseline.

Tier 4 full dogfood

With real SaaS Starter backend/database:

  • one setup command from empty sandbox;
  • checkout;
  • portal;
  • subscription lifecycle;
  • webhook signature verification;
  • event deduplication/out-of-order handling;
  • reconciliation;
  • no-diff second setup;
  • dynamic Codefly port/origin;
  • permission/approval/receipt visibility.

Security invariants

  • Local live credential cannot reach a mutating broker request.
  • Provider never sees raw Stripe credentials or signing secret.
  • Management key is not runtime output by default.
  • API-version replacement explicitly rotates/captures secret.
  • No URL auto-adoption.
  • Only exact owned/imported endpoint can be destroyed.
  • Unknown/non-idempotent outcomes are not blindly retried.
  • No Stripe branch enters Core/CLI.

Acceptance criteria

  • codefly-dev/provider-stripe exists with build/release/conformance.
  • Founder can start from an empty dedicated sandbox, run one setup command,
    review/apply, run billing flows, and rerun with no diff.
  • Stripe restricted-key practicality is recorded from live qualification, not
    assumed.
  • API-version change is correctly planned as REPLACE.
  • Lost-response/idempotency recovery creates no duplicate and preserves the
    one-time secret.
  • Local-forwarded mode uses actual Codefly endpoint and creates no remote
    endpoint.
  • Exact import/migration works without URL adoption.
  • Explicit destroy removes only owned test resource.
  • Starter shim contains no provider API/configuration write logic.
  • No key, secret, raw body, copied port, customer/email data, or unsafe account
    data appears in plan/state/logs/diagnostics/receipts/cassettes/Git.
  • Full starter billing dogfood and cleanup pass.
  • Stripe baseline opens Sentry and Resend implementation; protocol remains
    provisional until all three reference providers pass.

Explicit non-goals

  • Stripe products/prices/tax/meters/catalog management.
  • Customer/subscription resource management by provider setup.
  • Billing portal policy creation.
  • Production live-account mutation.
  • Owning Stripe CLI lifecycle in v0.
  • Browser Stripe.js/Elements keys.
  • General Stripe IaC.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthighHigh-priority autonomous implementation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions