Chrome extensions

Connect a Manifest V3 extension to your own Stripe account using authenticated CashSDK customer sessions and verified entitlements.

CashSDK supports Chrome extension pages and Manifest V3 service workers through cashsdk-web/extension. It uses the same customer identity, Stripe connection, products and entitlements as your website and directly distributed Mac app.

Install#

The extension entry point ships in cashsdk-web. Bundle it in the extension:

sh
npm install cashsdk-web
# or: pnpm add cashsdk-web
# or: yarn add cashsdk-web

Your backend issues the customer sessions with cashsdk-node. Install it there, never in the extension:

sh
npm install cashsdk-node
# or: pnpm add cashsdk-node
# or: yarn add cashsdk-node

Set up your app#

Choose Chrome extension when creating the app. In Your App → Web and Stripe, allow chrome-extension:// followed by your exact 32-character extension ID. Also allow an HTTPS origin for your hosted checkout return page. Wildcards, paths and opaque null origins are refused.

An unpacked build and the Chrome Web Store build have different extension IDs, unless you pin the ID with a key in the manifest. Allow the unpacked ID under Sandbox origins while you build, and the Web Store ID under Production origins when you publish.

Connect your own Stripe account under Marketplace using its API key and webhook signing secret. Import its Price IDs, map them to entitlements, add the products to an offering, and activate the connection. See Getting paid on the web. The funds and customers belong to your Stripe account.

Issue sessions from your backend#

Authenticate the caller with your existing sign-in system. The backend derives the customer ID from that authenticated session and fixes the extension origin in its configuration. Never accept a customer ID or arbitrary origin supplied by a page or content script.

ts
const session = await cashsdk.createWebSession({
  customerId: authenticatedUser.id,
  origin: configuredExtensionOrigin,
  scopes: [
    "customer:read", "entitlements:read", "subscriptions:read", "offerings:read",
    "checkout:create", "checkout:read", "billing:manage",
  ],
});
return Response.json(session, { headers: { "Cache-Control": "no-store" } });

cashsdk is a CashSDKServer from cashsdk-node, holding a restricted server key with Create customer web sessions (web_sessions:write) on your backend. Never put that key or a Stripe key in an extension. Sessions expire within ten minutes. The client keeps its session and asks your backend for a new one shortly before it expires; minting is rate limited, so do not ask for one per request.

Bundle the client#

Bundle all SDK JavaScript in the extension. Do not load remote scripts. Request host permissions for the CashSDK API and your backend, then create the client in an extension page or service worker:

ts
import { createExtensionCashSDK, openCheckoutTab } from "cashsdk-web/extension";

const cashsdk = createExtensionCashSDK({
  runtime: chrome.runtime,
  publishableKey: configuration.publishableKey,
  environment: "sandbox",
  getSessionToken: ({ signal }) => authenticatedBackend.cashSDKSession({ signal }),
});

await cashsdk.initialize();
const offerings = await cashsdk.getOfferings();
const access = await cashsdk.getEntitlements();

The adapter handles Chrome requests that omit the Origin header. The API still checks the session's exact extension origin, app, environment and scopes. A browser Origin header takes precedence over the extension fallback header.

Checkout and billing#

Call checkout from a user action. Persist an idempotency key before the request, scoped to the authenticated customer, app and environment. Save the returned checkout ID. Reuse them after popup closure, worker termination or a lost response: the same key returns the same checkout.

ts
const checkout = await cashsdk.checkout({
  packageId: selectedPackage.id,
  successUrl: configuration.hostedReturnUrl,
  cancelUrl: configuration.hostedReturnUrl,
}, { idempotencyKey: savedAttempt.key });

if (checkout.status === "requires_action") {
  await openCheckoutTab(chrome.tabs, checkout);
}

openCheckoutTab and openBillingPortalTab open only Stripe's pages: on Stripe's own hosts, or on your Stripe account's custom domain under Stripe's paths. Custom domains need cashsdk-web 1.0.0 or later.

Returns use an allowed hosted HTTPS page, never a chrome-extension:// callback. On reopening the extension, call getCheckout(savedAttempt.checkoutId) and getEntitlements(). Only verified entitlements unlock access. A successful redirect or a completed checkout is not an access grant.

Checkout sells your Stripe subscriptions and one-time products, applies free trials from your intro offers, and takes cards. When a checkout is refused, act on its code: already_subscribed opens the billing portal, already_owned shows the product as owned, already_purchased waits for access, and checkout_in_progress retries shortly. The full behavior is in Getting paid on the web.

Use openBillingPortal({ returnUrl }) and openBillingPortalTab(chrome.tabs, portal) for Stripe's customer portal. Configure the portal in your Stripe account first. On sign-out, call logout() and have your backend call revokeWebSessions(user.id). Keep content scripts away from billing credentials and privileged billing commands.

Runnable example#

Build cashsdk-web, then run node examples/chrome-extension/build.mjs from the source repository. Load examples/chrome-extension through Load unpacked on chrome://extensions. The popup displays its origin and accepts your backend endpoint and signed-in merchant token. Permissions are requested only for the two hosts you configure. The example stores the merchant token in storage.session and durable checkout attempts in storage.local; no provider secret is stored.

The integration test runs this extension in Chromium against both CashSDK services, PostgreSQL, Redis and signed Stripe test-double events.

Go live#

Before you publish to the Chrome Web Store, follow Go live with Stripe: the Production Stripe connection, live prices in your offering, the Web Store extension ID under Production origins, a live server key, environment: "production" in the client, and a first live purchase and refund.

References: Chrome network requests and Stripe Checkout.