Stripe App Integration

Purpose and Scope

The Stripe app package is the repository home for the Dub Conversions app distributed through the Stripe App Marketplace. Its purpose is to connect a Stripe account to a Dub workspace so payment activity can be attributed back to Dub conversion tracking and partner-program reporting. The first-party integration guide frames this as a sale-conversion workflow: Dub listens to Stripe payment lifecycle events such as recurring subscriptions, one-time payments, free trials, refunds, cancellations, churn, and usage expansion. In this repository, the package focuses on the Stripe app user interface, OAuth connection flow, secret storage, and workspace connection update needed before those conversion events can be associated with a workspace.

Sources: packages/stripe-app/README.md, packages/stripe-app/src/views/AppSettings.tsx, packages/stripe-app/src/utils/oauth.ts, packages/stripe-app/src/utils/dub.ts

The package should be read as an integration package rather than a general Dub web application module. It is private, has its own package manifest, depends on the Stripe UI extension SDK, and exposes local development and publishing tasks through the Stripe app tooling instead of the monorepo's public package publishing scripts. The app settings view is the operational center of the integration: it presents the connected workspace state, starts OAuth when no workspace is connected, exchanges authorization results for Dub tokens, and disconnects the workspace when requested by the Stripe user. That makes the package a compact bridge between Stripe's extension runtime and Dub's OAuth and integration APIs.

Relevant Source Files

  • packages/stripe-app/README.md — documents the package as the Stripe app for Dub Conversions and gives the local run and publish workflow.
  • packages/stripe-app/package.json — defines the private Stripe app package, runtime dependencies, Node engine requirement, lint and test scripts, and Stripe UI extension tooling.
  • packages/stripe-app/src/views/AppSettings.tsx — implements the app settings UI and coordinates connect, disconnect, workspace loading, token exchange, and secret persistence.
  • packages/stripe-app/src/utils/oauth.ts — builds Dub OAuth URLs, exchanges authorization codes, retrieves user information, validates stored access tokens, and refreshes expired tokens.
  • packages/stripe-app/src/utils/dub.ts — updates Dub with the Stripe account identifier and Stripe mode through the Dub Stripe integration endpoint.

System-to-Code Mapping

At a product level, the integration starts when a Stripe user installs Dub from the marketplace and opens the app settings surface in Stripe. The app settings component receives Stripe extension context, including user account information, OAuth context, and the current environment mode. It then uses workspace loading state to decide whether to render a connected workspace banner, show a sign-in flow, or continue an OAuth callback. This keeps the user-facing path simple: a workspace is either connected, actively connecting, or ready to start authorization. The code mirrors that mental model with separate state values for loading, connecting, disconnecting, OAuth state, and code challenge handling.

Sources: packages/stripe-app/src/views/AppSettings.tsx

The connection itself uses OAuth 2.0 with proof-key support. The app generates an OAuth state and challenge through Stripe's UI extension SDK, then constructs a Dub authorization URL using the Dub client identifier, a Stripe dashboard redirect URL, an authorization-code response type, the code challenge, and the generated state. The redirect URL is mode-aware: live mode points to the production Stripe app OAuth callback path, while non-live modes include the test dashboard path. This distinction matters because a sandbox installation should not accidentally bind the workspace to the wrong Stripe environment.

Sources: packages/stripe-app/src/utils/oauth.ts

Once the OAuth callback returns with a code and verifier, the app exchanges those values for a Dub token by posting form-encoded data to the Dub OAuth token endpoint. After the token is received, the app stores it in Stripe app secrets, retrieves the Dub workspace from the user information endpoint, sends the Stripe account identifier and mode to Dub, and then stores the workspace data as another Stripe secret. The ordering is important: the app needs a valid token before it can learn the workspace, and Dub must be updated with the Stripe account before the local connected-state cache is refreshed.

Sources: packages/stripe-app/src/views/AppSettings.tsx, packages/stripe-app/src/utils/oauth.ts, packages/stripe-app/src/utils/dub.ts

Execution Flow

A typical local or sandbox test begins by running the app with the Stripe CLI, opening the Stripe app surface, and choosing the connection action. If no workspace is already present, the settings view prepares a new OAuth state and challenge. The sign-in action links the user to Dub's authorization page. After the user authorizes the app, Stripe returns the OAuth code and verifier to the extension context, and the component automatically calls the connection routine. During this phase the user sees a large spinner, which is driven by either the workspace loading state or the explicit connecting state.

The disconnect path is deliberately symmetrical. The settings view asks for a valid Dub token, deletes both the stored workspace and token secrets from Stripe, and then calls the Dub integration update endpoint with a null account identifier. The request still includes the Stripe mode, choosing sandbox when the Stripe account reports sandbox status and otherwise using the extension environment mode. This makes disconnect a remote state change, not only a local cleanup action. After the remote update and local secret deletion complete, the workspace hook is mutated so the settings UI reflects the disconnected state.

API Components

The OAuth utility exposes a small set of integration-specific helpers. The authorization helper returns a Dub authorization URL. The token helper exchanges a code and verifier for a token. The user-info helper retrieves the current Dub workspace using a bearer access token. The valid-token helper reads the stored token secret and verifies it by calling the user-info endpoint; if verification fails, it attempts a refresh-token grant and stores the refreshed token. The refresh helper posts the client identifier and refresh token to the same OAuth token endpoint and returns the refreshed token on success.

Sources: packages/stripe-app/src/utils/oauth.ts

The Dub utility is intentionally narrow. It patches the Dub Stripe integration endpoint with the current Stripe account identifier and Stripe mode, authenticating with the Dub bearer access token. When connecting, the account identifier is the active Stripe account from the extension context. When disconnecting, the identifier is null. If the Dub endpoint returns a non-success response, the utility reads the error payload and throws a workspace update error. This gives the settings view a single function for both binding and unbinding the external account while keeping endpoint details outside the React component.

Sources: packages/stripe-app/src/utils/dub.ts

Local Running and Publishing

The README documents the local development entry point as a Stripe app command run from the Stripe app package context. Contributors should authenticate the Stripe CLI and use the Stripe app tooling rather than expecting this package to behave like the main Next.js application. For a quick local run, navigate to the Stripe app package and start the app through the Stripe CLI:

cd packages/stripe-app
stripe apps start

Publishing is also handled through Stripe tooling. The documented release sequence is to log in with the Stripe CLI to the Dub Technologies account, move into the package directory, increment the version field in the Stripe app manifest, upload the app with the Stripe CLI, and publish the uploaded version from the Stripe dashboard. The package manifest version is separate evidence of package state, but the README specifically calls out the Stripe app manifest version as the release value to increment. Treat publishing as a marketplace release workflow, not as an npm publish flow.

stripe login
cd packages/stripe-app
# increment the version field in stripe-app.json
stripe apps upload
# publish the uploaded version in the Stripe dashboard

Sources: packages/stripe-app/README.md, packages/stripe-app/package.json

Package Reference

AreaConcrete value or behavior
Package namecom.example.dub
Current package version0.0.24
Visibilityprivate package
Package license fieldproprietary marker
Runtime dependenciesStripe UI extension SDK, React, React DOM, Stripe SDK
Engine requirementNode.js 14 or newer
Scriptslint runs ESLint over TypeScript sources; test runs Jest
Main local commandstripe apps start
Main publish commandstripe apps upload

The package manifest reinforces the integration boundary. The dependencies are centered on Stripe UI extensions and React rendering, with the Stripe SDK available for Stripe API interactions. The eslint configuration extends Stripe's UI extension rules, which is a useful signal that UI extension constraints should be respected when changing the app. The Node engine requirement is lower than the repository's recommended contributor runtime, so contributors should follow the broader repository setup for monorepo work but remember that this integration also has Stripe-specific runtime and tooling assumptions.

Sources: packages/stripe-app/package.json

Implementation Notes and Next Steps

When modifying this app, keep the connection lifecycle atomic from a user's perspective. A partial connection can leave a token stored without a workspace, or a remote Dub workspace bound without the local Stripe secret cache reflecting it. The existing flow reduces that risk by waiting for a token, fetching workspace information, updating Dub with the Stripe account, storing the workspace, and finally mutating the workspace hook. Error handling is stricter in the utility functions than in some component branches, so changes that add user-visible failures should preserve the current remote-update and secret-cleanup order.

For related reading, start with the conversion tracking and Track API pages to understand what sale events become once Stripe data reaches Dub. Then read the API authentication page for the OAuth endpoints used by this app, and the publishing packages page for how this integration's marketplace workflow differs from npm package publishing. If you are testing a partner-program setup, also review commissions and payouts because the official Stripe integration guide connects refunds to voiding partner commissions, which makes accurate account binding and mode selection important before testing real payment flows.