npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cedros/pay-react

v1.1.32

Published

React frontend library for Cedros Pay - unified Stripe and Solana x402 payments

Readme

Cedros Pay

npm version License: MIT TypeScript Bundle Size Stripe-only: <100KB Tests

Three payment rails. One React component.

Stripe for cards. Solana USDC for wallets. Credits for cedros-login users. Uses your existing products. No deposit wallets. No monitoring. No custom crypto backend.


Ecommerce UI (Cedros Shop)

This repo now includes a provider-agnostic storefront UI layer (catalog + cart + checkout orchestration) built in a shadcn-inspired style.

  • Docs: docs/ecommerce/README.md
  • Import: import { ecommerce } from '@cedros/pay-react'

What is Cedros Pay?

One component, three payment rails. Stripe for cards, Solana USDC for wallets, Credits for account balances. No second checkout.

Cedros Pay connects traditional payments (Stripe) with crypto (Solana x402) and account credits (cedros-login) using the product IDs you already have. No deposit wallets to manage. No wallet infrastructure to secure. No custody risk.

The Problem with Traditional Crypto Payments

Adding crypto to your store traditionally requires:

  • Creating deposit wallets per user
  • Monitoring deposits 24/7
  • Sweeping funds to your merchant wallet
  • Storing wallet state in your database
  • Building Stripe separately
  • Maintaining two systems

The Cedros Pay Solution

<CedrosPay
  resource="your-product-id" // from your DB
  onPaymentSuccess={(txId) => unlockContent(txId)}
/>

How It Works (x402)

x402 makes Solana payments stateless. The client includes a signed transaction with the request. Your backend verifies it on-chain and unlocks the resource.

Flow:

Traditional:
User → Deposit Wallet (you manage) → Monitor → Sweep → Merchant
         ↓
  Store state in DB
         ↓
  Custody risk

x402:
User → Sign Transaction → Your Backend → Verify On-Chain → Merchant (Direct)

Three key benefits:

  • No deposit wallets
  • No sweeping funds
  • No payment state in your DB

Key Features

1. One Component, Three Rails

Stripe for cards, Solana USDC for wallets, Credits for account balances. No second checkout.

<CedrosPay resource="product-1" />
// All three payment methods work

2. Works With Your Products

Pass your DB ID. No new schema.

<CedrosPay resource="existing-product-123" />
// resource = your database primary key

3. Real Ecommerce, Not Just a Button

Carts, coupons, refunds, metadata.

<CedrosPay
  items={[{ resource: "item-1", quantity: 2 }]}
  couponCode="LAUNCH50"
/>

4. Auto-Detects Payment Options

Shows available payment methods based on user context.

// User without wallet, not logged in:
[Pay with Card]

// User with Phantom wallet:
[Pay with Card] [Pay with Crypto]

// User logged into cedros-login:
[Pay with Card] [Pay with Crypto] [Pay with Credits]

5. Agent-Ready

x402 over HTTP; agents pay per request.

GET /api/premium-data
X-PAYMENT: <signed-transaction>
# Agent gets data instantly

6. Credits Payment (cedros-login)

Let users pay with account credits. Instant, zero-fee payments.

import { useCedrosLogin } from '@cedros/login-react';

function Checkout() {
  const { authToken } = useCedrosLogin();

  return (
    <CedrosPay
      resource="product-1"
      display={{ showCredits: !!authToken }}
      checkout={{ authToken }}
    />
  );
}

7. Self-Host or Roll Your Own

React UI + Rust backend. Open API.

<CedrosPay resource="item" wallets={customWallets} renderModal={CustomModal} />

Additional Features:

  • 🌍 Open source — MIT-licensed and extensible
  • 🔐 Stateless & secure — No user accounts or deposit addresses required
  • 🧱 Minimal integration — Middleware or proxy for Rust APIs

Quick Start (3 Steps in ~3 Minutes)

If you can wrap a provider, you can ship triple-rail payments.

Step 1: Install

Option 1: Stripe + Crypto (Full Features)

npm install @cedros/pay-react \
  @solana/web3.js \
  @solana/spl-token \
  @solana/wallet-adapter-base \
  @solana/wallet-adapter-react \
  @solana/wallet-adapter-react-ui \
  @solana/wallet-adapter-wallets

Option 2: Stripe Only (Smaller Bundle - ~75KB)

npm install @cedros/pay-react

Use the stripe-only entry point to get a dramatically smaller bundle:

import { CedrosProvider, StripeButton } from "@cedros/pay-react/stripe-only";
import "@cedros/pay-react/style.css";

function App() {
  return (
    <CedrosProvider
      config={{
        stripePublicKey: "pk_test_...",
        serverUrl: window.location.origin,
        solanaCluster: "mainnet-beta",
      }}
    >
      <StripeButton resource="item-id" />
    </CedrosProvider>
  );
}

CDN Usage (Optional)

For zero-build prototyping or simple sites, you can import directly from a CDN:

<!-- Styles -->
<link
  rel="stylesheet"
  href="https://unpkg.com/@cedros/[email protected]/dist/style.css"
/>

<!-- Library (ESM) -->
<script type="module">
  import {
    CedrosProvider,
    StripeButton,
  } from "https://unpkg.com/@cedros/[email protected]/dist/index.mjs";
  // Your code here
</script>

CDN Options:

Performance Notes:

  • CDN providers (unpkg, jsdelivr) automatically serve with immutable cache headers (Cache-Control: public, max-age=31536000, immutable)
  • For self-hosted deployments, set the same cache headers on /dist/* assets for optimal performance
  • Pin to specific version (@0.1.0) in production to ensure stability

Option 3: Crypto Only

If you only need Solana crypto payments:

npm install @cedros/pay-react \
  @solana/web3.js \
  @solana/spl-token \
  @solana/wallet-adapter-base \
  @solana/wallet-adapter-react \
  @solana/wallet-adapter-react-ui \
  @solana/wallet-adapter-wallets

Then use the crypto-only entry point:

import { CedrosProvider, CryptoButton } from "@cedros/pay-react/crypto-only";
import "@cedros/pay-react/style.css";

function App() {
  return (
    <CedrosProvider
      config={{
        stripePublicKey: "pk_test_...", // Required even for crypto-only (use a placeholder)
        serverUrl: window.location.origin,
        solanaCluster: "mainnet-beta",
      }}
    >
      <CryptoButton resource="item-id" />
    </CedrosProvider>
  );
}

Note: Even when using the crypto-only entry point, stripePublicKey is still required in the config (use a test/placeholder key if you don't have Stripe integration). This is a known limitation that will be addressed in a future version.

Using the full bundle but hiding crypto button:

<CedrosPay resource="item-id" display={{ showCrypto: false }} />

Step 2: Configure Provider

Wrap your app with credentials + cluster:

import { CedrosProvider } from "@cedros/pay-react";
import "@cedros/pay-react/style.css";

function App() {
  return (
    <CedrosProvider
      config={{
        stripePublicKey: "pk_test_...",
        serverUrl: "https://your-api.com",
        solanaCluster: "mainnet-beta",
      }}
    >
      <YourApp />
    </CedrosProvider>
  );
}

Step 3: Drop in the Component

On success → fulfill order:

import { CedrosPay } from "@cedros/pay-react";

function Checkout() {
  return (
    <CedrosPay
      resource="your-product-id"
      callbacks={{
        onPaymentSuccess: (result) => {
          // Unlock content / fulfill order
          unlockContent(result.transactionId);
        },
      }}
    />
  );
}

Backend options: Use the Rust server, or implement the open API.

Links:

Cross-Domain Backend (Optional): If your backend is on a different domain (e.g., api.example.com while your frontend is on example.com), explicitly set serverUrl:

<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: "https://api.example.com", // Explicit URL for cross-domain
    solanaCluster: "mainnet-beta",
  }}
>
  {/* ... */}
</CedrosProvider>

Backend Setup

Your backend must implement the Cedros Pay API endpoints:

cargo install cedros-pay-server

Required Endpoints (v2.0.0+):

  • GET /cedros-health - Health check and route discovery
  • POST /paywall/v1/quote - x402 payment quote (resource ID in body)
  • POST /paywall/v1/verify - Payment verification (resource ID in X-PAYMENT header)
  • POST /paywall/v1/stripe-session - Create Stripe checkout (single item)
  • GET /paywall/v1/stripe-session/verify - Verify Stripe payment session (security-critical)
  • POST /paywall/v1/cart/checkout - Create Stripe checkout (cart)
  • POST /paywall/v1/subscription/stripe-session - Create Stripe subscription checkout
  • POST /paywall/v1/subscription/stripe-mobile-session - Create Stripe-native mobile subscription session
  • POST /paywall/v1/cart/quote - Get x402 quote for cart items
  • POST /paywall/v1/gasless-transaction - Build gasless transaction (optional)
  • POST /paywall/v1/nonce - Generate nonce for admin authentication
  • POST /paywall/v1/refunds/request - Create refund request (requires signature from original payer or admin)
  • POST /paywall/v1/refunds/pending - Get all pending refunds (admin-only, requires nonce)
  • POST /paywall/v1/refunds/approve - Get fresh quote for pending refund (admin-only)
  • POST /paywall/v1/refunds/deny - Deny pending refund (admin-only)

Turnkey cross-channel setup

Cedros is designed so you create the product once in the admin dashboard and then let the packages choose the right payment rail for web, Apple App Store, and Google Play at runtime.

For the full operator runbook, see ui/docs/cross-channel-setup.md.

  1. In Admin → Products, create the product and set:
    • the normal commerce fields such as title, price, fulfillment, and checkout requirements
    • the Store policy classification such as digital_in_app, physical_goods, real_world_service, or reader_content
    • the store-managed product kind plus any Apple product ID or Google Play product/base-plan mapping when the product is sold inside app-store builds
  2. In Admin → Payment Options → Stripe, add your Stripe API keys, webhook secret, and any mobile redirect URI schemes you use.
  3. In Admin → Payment Options → App Stores, add:
    • Apple issuer ID, key ID, private key, and bundle ID
    • Google service-account credentials, package name, RTDN push identity, and push audience
  4. In Stripe, App Store Connect, and Google Play Console, create the matching products and point their webhooks / notifications at your Cedros server endpoints.
  5. In your React Native app, set Cedros provider config once for the build, especially distributionChannel, and Cedros will hydrate the product catalog from /paywall/v1/products automatically unless you disable catalog sync.

Manual setup that still matters

  • Stripe: Use the Cedros Stripe webhook endpoint so subscription and Checkout lifecycle events reach Cedros.
  • Apple: Create matching App Store Connect products and configure App Store Server Notifications to your tenant-scoped Cedros Apple notification URL.
  • Google: Create matching Play Console products and configure Real-time developer notifications to your tenant-scoped Cedros Google notification URL.
  • Store policy exceptions: Only enable reader-app, external-link, User Choice Billing, Alternative Billing Only, or external-offers behavior when your app is actually approved for those storefront programs.

Example - Quote Request:

POST /paywall/v1/quote
Content-Type: application/json

{
  "resource": "premium-article",
  "couponCode": "SAVE20"  # optional
}

# Response: 402 Payment Required with x402 quote

Example - Payment Verification:

POST /paywall/v1/verify
X-PAYMENT: <base64-encoded-payment-proof>

# Payment proof includes resource ID and type
# No resource IDs in URL path (security improvement)

Example - Refund Request:

POST /paywall/v1/refunds/request
Content-Type: application/json
X-Signature: <base64-encoded-signature>
X-Message: request-refund:<transaction-signature>
X-Signer: <wallet-address>

{
  "originalPurchaseId": "5jHxP...2QvK",  // Original transaction signature
  "recipientWallet": "9xQeW...Yhq",
  "amount": 10.5,
  "token": "USDC",
  "reason": "Customer requested refund"
}

# Signer must be the original payer OR admin wallet
# Recipient wallet must match the payer from original transaction
# Only one refund allowed per transaction signature

React Native Stripe subscriptions

  • Cedros now supports two explicit Stripe subscription flows on mobile.
  • Hosted redirect checkout remains the stock default:
{
  "flow": "redirect_checkout",
  "sessionId": "cs_test_123",
  "url": "https://checkout.stripe.com/c/pay/cs_test_123"
}
  • @cedros/pay-react-native opens url automatically in the device browser for this flow.
  • Native mobile subscriptions use a dedicated PaymentSheet contract from POST /paywall/v1/subscription/stripe-mobile-session:
{
  "flow": "payment_sheet",
  "subscriptionId": "sub_123",
  "customerId": "cus_123",
  "customerEphemeralKeySecret": "ephkey_123",
  "paymentIntentClientSecret": "pi_123_secret_abc"
}
  • In React Native, use SubscribeButton with flow="payment_sheet" or call subscriptionManager.processMobileSubscription(...) directly when you want the native Stripe sheet.
  • Set stripeReturnUrl in CedrosProvider for PaymentSheet app return handling on iOS, for example covenant://stripe-return.
  • If Stripe should return to your app via a custom deep link such as covenant://subscription/success, allowlist the URI scheme on the server with stripe.allowed_redirect_schemes or CEDROS_STRIPE_ALLOWED_REDIRECT_SCHEMES=covenant.
  • Universal links such as https://app.example.com/subscription/success do not need to be allowlisted.

React Native store-aware product sync

  • @cedros/pay-react-native can now hydrate its provider-level store policy catalog from GET /paywall/v1/products.
  • By default, Cedros merges the server catalog with any manually supplied paymentPolicy.productCatalog, with manual entries taking precedence.
  • This means the host app can usually pass just the product id to CedrosPay while keeping the canonical fulfillment and store-product mapping in the Cedros admin dashboard.
  • If you need to disable that behavior, set paymentPolicy.productCatalogSync.enabled to false.

Example - Get Pending Refunds (Admin - Nonce Required):

# Step 1: Generate nonce
POST /paywall/v1/nonce
Content-Type: application/json

{
  "purpose": "list-pending-refunds"
}
# Response: { "nonce": "abc123...", "expiresAt": 1234567890 }

# Step 2: Fetch pending refunds with nonce
POST /paywall/v1/refunds/pending
Content-Type: application/json
X-Signature: <base64-encoded-signature>
X-Message: list-pending-refunds:<nonce>
X-Signer: <admin-wallet-address>

# Returns array of pending refund requests
# Response: [{ refundId, originalPurchaseId, recipientWallet, amount, token, reason, ... }]

See Backend Integration and @backend-migration-resource-leakage.md for complete API reference and migration guide.


Production Deployment

Content Security Policy (CSP) Headers

⚠️ Important: Cedros Pay requires specific Content Security Policy directives to function correctly in production. Without these, Stripe and Solana RPC calls will be blocked by the browser.

Required CSP Directives

Content-Security-Policy:
  script-src 'self' https://js.stripe.com;
  connect-src 'self' https://api.stripe.com https://*.stripe.com https://api.mainnet-beta.solana.com https://*.solana.com;
  frame-src https://js.stripe.com https://checkout.stripe.com;

Breakdown:

  • script-src - Allows Stripe.js to load and execute
  • connect-src - Allows API calls to Stripe and Solana RPC endpoints
  • frame-src - Allows Stripe Checkout iframe to load

Framework-Specific Examples

Next.js (App Router)

// next.config.js
const nextConfig = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          {
            key: "Content-Security-Policy",
            value: [
              "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com",
              "connect-src 'self' https://api.stripe.com https://*.stripe.com https://api.mainnet-beta.solana.com https://*.solana.com https://*.helius-rpc.com https://*.quicknode.pro",
              "frame-src https://js.stripe.com https://checkout.stripe.com",
            ].join("; "),
          },
        ],
      },
    ];
  },
};

Next.js (Pages Router with Middleware)

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const response = NextResponse.next();

  response.headers.set(
    "Content-Security-Policy",
    [
      "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com",
      "connect-src 'self' https://api.stripe.com https://*.stripe.com https://api.mainnet-beta.solana.com https://*.solana.com",
      "frame-src https://js.stripe.com https://checkout.stripe.com",
    ].join("; ")
  );

  return response;
}

Vite (Development)

// vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    headers: {
      "Content-Security-Policy": [
        "script-src 'self' 'unsafe-inline' https://js.stripe.com",
        "connect-src 'self' https://api.stripe.com https://*.stripe.com https://api.mainnet-beta.solana.com https://*.solana.com",
        "frame-src https://js.stripe.com https://checkout.stripe.com",
      ].join("; "),
    },
  },
});

Nginx

# nginx.conf
location / {
  add_header Content-Security-Policy "script-src 'self' https://js.stripe.com; connect-src 'self' https://api.stripe.com https://*.stripe.com https://api.mainnet-beta.solana.com https://*.solana.com; frame-src https://js.stripe.com https://checkout.stripe.com;" always;
}

Express.js

// server.js
const helmet = require("helmet");

app.use(
  helmet.contentSecurityPolicy({
    directives: {
      scriptSrc: ["'self'", "https://js.stripe.com"],
      connectSrc: [
        "'self'",
        "https://api.stripe.com",
        "https://*.stripe.com",
        "https://api.mainnet-beta.solana.com",
        "https://*.solana.com",
      ],
      frameSrc: ["https://js.stripe.com", "https://checkout.stripe.com"],
    },
  })
);

HTML Meta Tag (Not Recommended)

<!-- Use server headers instead when possible -->
<meta
  http-equiv="Content-Security-Policy"
  content="script-src 'self' https://js.stripe.com; connect-src 'self' https://api.stripe.com https://*.stripe.com https://api.mainnet-beta.solana.com https://*.solana.com; frame-src https://js.stripe.com https://checkout.stripe.com;"
/>

Custom RPC Endpoints

If you're using a custom Solana RPC provider (Helius, QuickNode, etc.), add their domains to connect-src:

connect-src 'self'
  https://api.stripe.com
  https://*.stripe.com
  https://mainnet.helius-rpc.com
  https://*.quicknode.pro
  https://rpc.ankr.com;

Devnet/Testnet

For development against Solana devnet or testnet:

connect-src 'self'
  https://api.stripe.com
  https://*.stripe.com
  https://api.devnet.solana.com
  https://api.testnet.solana.com;

Troubleshooting CSP Issues

Symptom: Stripe Checkout doesn't load or throws CORS errors

Refused to load the script 'https://js.stripe.com/v3/' because it violates the following Content Security Policy directive: "script-src 'self'"

Fix: Add https://js.stripe.com to script-src


Symptom: Solana RPC calls fail with network errors

Refused to connect to 'https://api.mainnet-beta.solana.com' because it violates the following Content Security Policy directive: "connect-src 'self'"

Fix: Add your Solana RPC endpoint to connect-src


Symptom: Stripe Checkout redirects fail or show blank page

Refused to display 'https://checkout.stripe.com' in a frame because it violates the following Content Security Policy directive: "frame-src 'self'"

Fix: Add https://checkout.stripe.com to frame-src


Testing CSP in Development

  1. Open browser DevTools → Console
  2. Look for CSP violation warnings (usually in red)
  3. Check the Network tab for blocked requests
  4. Add blocked domains to appropriate CSP directives

Chrome DevTools Example:

[Report Only] Refused to connect to 'https://api.stripe.com/v1/tokens'
because it violates the document's Content Security Policy.

Best Practices

DO:

  • Use server-side headers (not meta tags) for CSP
  • Test CSP in staging before deploying to production
  • Use wildcards sparingly (*.stripe.com is okay, * is not)
  • Include your custom RPC provider domains

DON'T:

  • Use 'unsafe-inline' in production unless necessary
  • Block Stripe or Solana domains
  • Forget to add frame-src for Stripe Checkout
  • Use overly permissive directives like * 'unsafe-eval'

CSP Helper Generator

⚠️ RECOMMENDED: Use the generateCSP() helper to automatically generate correct CSP directives for your configuration. This prevents common misconfigurations that break payment widgets.

Quick Start

import { generateCSP, RPC_PROVIDERS } from "@cedros/pay-react";

// Generate CSP for production with custom RPC
const csp = generateCSP({
  solanaCluster: "mainnet-beta",
  solanaEndpoint: "https://mainnet.helius-rpc.com",
  allowUnsafeScripts: true, // Required for Next.js
});

// Use in your framework
response.setHeader("Content-Security-Policy", csp);

Configuration Options

interface CSPConfig {
  solanaCluster?: "mainnet-beta" | "devnet" | "testnet";
  solanaEndpoint?: string; // Custom RPC URL
  customRpcProviders?: string[]; // Additional RPC providers
  allowUnsafeScripts?: boolean; // For Next.js, etc.
  additionalScriptSrc?: string[];
  additionalConnectSrc?: string[];
  additionalFrameSrc?: string[];
  includeStripe?: boolean; // Set false for crypto-only
}

Framework Examples

Next.js App Router:

// next.config.js
import { generateCSP } from "@cedros/pay-react";

const csp = generateCSP({
  solanaCluster: "mainnet-beta",
  solanaEndpoint: process.env.SOLANA_RPC_URL,
  allowUnsafeScripts: true,
});

const nextConfig = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [{ key: "Content-Security-Policy", value: csp }],
      },
    ];
  },
};

export default nextConfig;

Express with Helmet:

import { generateCSP } from "@cedros/pay-react";
import helmet from "helmet";

const cspDirectives = generateCSP(
  {
    solanaCluster: "mainnet-beta",
    solanaEndpoint: process.env.SOLANA_RPC_URL,
  },
  "helmet" // Returns object format for helmet
);

app.use(helmet.contentSecurityPolicy({ directives: cspDirectives }));

Vite Development:

// vite.config.ts
import { defineConfig } from "vite";
import { generateCSP } from "@cedros/pay-react";

const csp = generateCSP({
  solanaCluster: "devnet",
  allowUnsafeScripts: true,
});

export default defineConfig({
  server: {
    headers: {
      "Content-Security-Policy": csp,
    },
  },
});

Presets

Use presets for common scenarios:

import { generateCSP, CSP_PRESETS } from "@cedros/pay-react";

// Production mainnet with custom RPC
const csp1 = generateCSP(
  CSP_PRESETS.MAINNET_CUSTOM_RPC("https://mainnet.helius-rpc.com")
);

// Next.js with mainnet
const csp2 = generateCSP(
  CSP_PRESETS.MAINNET_NEXTJS("https://mainnet.helius-rpc.com")
);

// Devnet testing
const csp3 = generateCSP(CSP_PRESETS.DEVNET());

// Crypto-only (no Stripe)
const csp4 = generateCSP(CSP_PRESETS.CRYPTO_ONLY());

// Stripe-only (no Solana)
const csp5 = generateCSP(CSP_PRESETS.STRIPE_ONLY());

Common RPC Providers

import { RPC_PROVIDERS } from "@cedros/pay-react";

const csp = generateCSP({
  customRpcProviders: [
    RPC_PROVIDERS.HELIUS, // https://*.helius-rpc.com
    RPC_PROVIDERS.QUICKNODE, // https://*.quicknode.pro
    RPC_PROVIDERS.FLUX, // https://*.fluxrpc.com
    RPC_PROVIDERS.TRITON, // https://*.rpcpool.com
  ],
});

Output Formats

The helper supports multiple output formats:

// HTTP header format (default)
const header = generateCSP(config, "header");
// "script-src 'self' https://js.stripe.com; connect-src ..."

// HTML meta tag format
const meta = generateCSP(config, "meta");

// Next.js config format
const nextjs = generateCSP(config, "nextjs");

// Express helmet format (object)
const helmet = generateCSP(config, "helmet");
// { scriptSrc: [...], connectSrc: [...], frameSrc: [...] }

// Nginx config format
const nginx = generateCSP(config, "nginx");

// Raw directives object
const directives = generateCSP(config, "directives");

Why Use the Helper?

Prevents common errors:

  • Forgetting Solana RPC endpoints
  • Missing Stripe iframe domains
  • Wrong cluster URLs (devnet vs mainnet)

Type-safe configuration:

  • TypeScript autocomplete for all options
  • Validates cluster names
  • Catches typos at compile time

Framework-agnostic:

  • Works with Next.js, Express, Vite, Nginx, etc.
  • Multiple output formats
  • No dependencies

Security Best Practices

Subresource Integrity (SRI) for Stripe.js

⚠️ IMPORTANT: Cedros Pay does NOT use SRI hashes for Stripe.js, and this is intentional.

Why SRI is NOT used:

  • Stripe updates frequently - Security patches and bug fixes are pushed without URL changes
  • SRI breaks automatic updates - Hardcoded hashes prevent receiving critical security fixes
  • Stripe's official recommendation - Stripe explicitly advises against using SRI
  • Alternative protection - Content Security Policy (CSP) provides the security layer

From Stripe's documentation:

"We do not recommend using Subresource Integrity (SRI) with Stripe.js. Stripe.js is served from a highly-available CDN, and we regularly update the library to address security issues and improve functionality. Using SRI would prevent you from receiving these automatic updates."

How Cedros Pay Protects Against CDN Compromise:

  1. Content Security Policy (CSP)

    Content-Security-Policy: script-src 'self' https://js.stripe.com
    • Prevents loading scripts from unauthorized domains
    • Blocks inline scripts and eval()
    • Works with Stripe's automatic updates
  2. Package Integrity via npm

    {
      "dependencies": {
        "@stripe/stripe-js": "^2.4.0"
      }
    }
    • package-lock.json contains integrity hashes for npm packages
    • npm verifies package integrity on installation
    • Protects against tampering with the loader
  3. HTTPS Enforcement

    • Stripe.js is loaded over HTTPS only
    • Modern browsers enforce secure connections
    • Certificate pinning via browser trust store
  4. Version Pinning (optional)

    {
      "dependencies": {
        "@stripe/stripe-js": "2.4.0" // Exact version (no caret)
      }
    }
    • Prevents unexpected updates
    • Review changelog before upgrading
    • Balance security updates vs. stability

Recommended Security Checklist:

DO:

  • Use CSP headers with script-src https://js.stripe.com
  • Keep @stripe/stripe-js updated for security patches
  • Use HTTPS for all connections
  • Enable npm package auditing (npm audit)
  • Review Stripe's changelog before major updates
  • Monitor Stripe's security advisories

DON'T:

  • Add SRI hashes to Stripe.js (breaks updates)
  • Allow script-src * in CSP (too permissive)
  • Use outdated versions of @stripe/stripe-js
  • Load Stripe.js from third-party CDNs
  • Disable HTTPS enforcement

Alternative: Self-Hosting Stripe.js (NOT RECOMMENDED)

While technically possible to self-host Stripe.js with SRI, Stripe strongly discourages this:

  • ❌ Miss critical security updates
  • ❌ Break PCI DSS compliance requirements
  • ❌ Lose Stripe's CDN performance benefits
  • ❌ Violate Stripe's Terms of Service

For maximum security, follow Stripe's recommendations and use CSP instead of SRI.


🌍 Internationalization (i18n)

Cedros Pay supports multiple languages with automatic browser locale detection and zero-configuration setup.

Supported Languages

Currently available (auto-detected from src/i18n/translations/ folder):

  • 🇺🇸 English (en) - Default
  • 🇪🇸 Spanish (es)

Usage

Automatic (recommended):

import { useTranslation } from "@cedros/pay-react";

function PaymentButton() {
  const { t } = useTranslation(); // Auto-detects browser language

  return (
    <button>{t("ui.pay_with_card")}</button> // "Pay with Card" or "Pagar con Tarjeta"
  );
}

Manual locale override:

function SpanishOnlyButton() {
  const { t } = useTranslation("es"); // Force Spanish
  return <button>{t("ui.pay_with_card")}</button>; // Always "Pagar con Tarjeta"
}

Error messages (automatic):

import { PaymentError } from "@cedros/pay-react";

// Errors are automatically localized based on user's browser language
error.getUserMessage(); // Returns localized message + action
error.getShortMessage(); // Returns just the message (no action)
error.getAction(); // Returns just the action guidance

Available Translation Keys

UI Labels:

  • ui.pay_with_card - "Pay with Card"
  • ui.pay_with_crypto - "Pay with USDC"
  • ui.connect_wallet - "Connect Wallet"
  • ui.processing - "Processing..."
  • ui.loading - "Loading..."
  • ui.close - "Close"
  • ui.cancel - "Cancel"
  • ui.confirm - "Confirm"
  • ui.retry - "Try Again"
  • ui.contact_support - "Contact Support"

Error Messages:

t("errors.insufficient_funds_token.message"); // "Insufficient balance in your wallet"
t("errors.insufficient_funds_token.action"); // "Add more funds to your wallet and try again."

Wallet Messages:

t("wallet.no_wallet_detected"); // "No Solana wallet detected"
t("wallet.install_wallet"); // "Please install a Solana wallet..."
t("wallet.wallet_connection_failed"); // "Failed to connect wallet"

Adding New Languages

  1. Create translation file in src/i18n/translations/{locale}.json:

    // src/i18n/translations/fr.json
    {
      "locale": "fr",
      "ui": {
        "pay_with_card": "Payer par Carte",
        "pay_with_crypto": "Payer avec USDC",
        ...
      },
      "errors": { ... }
    }
  2. That's it! The system automatically detects new files and loads them.

See src/i18n/TRANSLATION_INSTRUCTIONS.md for detailed translation guidelines.

Dynamic Language Loading

The i18n system:

  • ✅ Auto-detects available languages from file system
  • ✅ Only loads the language the user needs (tree-shakeable)
  • ✅ Falls back to English if translation missing
  • ✅ Zero configuration required

Type Versioning Policy

Cedros Pay uses semantic versioning for TypeScript types to prevent breaking changes from affecting your code.

How It Works

All types are exported in versioned namespaces (v1, v2, etc.):

// Recommended: Use top-level exports (always points to current stable version)
import { X402Requirement, PaymentResult } from "@cedros/pay-react";

// Explicit version (locks to v1, won't break on v2 release)
import { v1 } from "@cedros/pay-react";
const requirement: v1.X402Requirement = {
  /* ... */
};

// Future: When v2 is released, you can migrate gradually
import { v2 } from "@cedros/pay-react";
const newRequirement: v2.X402Requirement = {
  /* ... */
};

Breaking Change Example

If we need to change X402Requirement.maxAmountRequired from string to bigint:

  1. v1 namespace remains unchanged - Your existing code keeps working
  2. v2 namespace is created with the new type
  3. Top-level exports point to v2 (with major version bump)
  4. You can migrate at your own pace:
// Your old code still works with v1
import { v1 } from "@cedros/pay-react";
const oldReq: v1.X402Requirement = { maxAmountRequired: "1000000" };

// New code uses v2
import { v2 } from "@cedros/pay-react";
const newReq: v2.X402Requirement = { maxAmountRequired: 1000000n };

Stability Guarantee

  • v1 types are frozen - No breaking changes, ever
  • Top-level exports may change across major versions
  • Older versions remain available for backward compatibility
  • Clear migration path when breaking changes are necessary

📖 Core Concepts

Single Item Purchase

<CedrosPay
  resource="article-123"
  callbacks={{
    onPaymentSuccess: (result) => unlockContent(result.transactionId),
  }}
/>

Cart Checkout (Multiple Items)

<CedrosPay
  items={[
    { resource: "product-1", quantity: 2 },
    { resource: "product-2", quantity: 1 },
  ]}
  callbacks={{
    onPaymentSuccess: (result) => processOrder(result.transactionId),
  }}
/>

Subscriptions

Cedros Pay supports recurring subscriptions with both Stripe and crypto (x402) payments.

Stripe Subscription Checkout:

import { SubscribeButton } from "@cedros/pay-react";

<SubscribeButton
  resource="plan-pro"
  interval="monthly"
  trialDays={14}
  onSubscriptionSuccess={(result) => {
    console.log("Subscribed:", result.subscriptionStatus);
  }}
/>;

Crypto Subscription (x402):

import { CryptoSubscribeButton } from "@cedros/pay-react";

<CryptoSubscribeButton
  resource="plan-pro"
  interval="monthly"
  onSubscriptionSuccess={(result) => {
    console.log("Subscription active until:", result.expiresAt);
  }}
/>;

Credits Subscription (cedros-login):

import { CreditsSubscribeButton } from "@cedros/pay-react";

<CreditsSubscribeButton
  resource="plan-pro"
  interval="monthly"
  authToken={user.cedrosToken}
  userId={user.id}
  autoCheckStatus
  onSubscriptionSuccess={(result) => {
    console.log("Subscription active until:", result.expiresAt);
  }}
/>;

Subscription Management (Upgrade/Downgrade/Cancel):

import { SubscriptionManagementPanel } from "@cedros/pay-react";

<SubscriptionManagementPanel
  resource="plan-pro"
  userId="[email protected]"
  availablePlans={[
    { resource: "plan-basic", name: "Basic", price: 999, currency: "USD", interval: "monthly" },
    { resource: "plan-pro", name: "Pro", price: 1999, currency: "USD", interval: "monthly" },
    { resource: "plan-enterprise", name: "Enterprise", price: 4999, currency: "USD", interval: "monthly" },
  ]}
  onSubscriptionChanged={(newResource) => console.log("Changed to:", newResource)}
  onSubscriptionCanceled={() => console.log("Subscription canceled")}
  showBillingPortal
/>;

Programmatic Subscription Management:

import { useSubscriptionManagement } from "@cedros/pay-react";

function SubscriptionSettings({ userId }: { userId: string }) {
  const {
    subscription,
    status,
    loadSubscription,
    previewChange,
    changeSubscription,
    cancelSubscription,
    openBillingPortal,
  } = useSubscriptionManagement();

  useEffect(() => {
    loadSubscription("plan-pro", userId);
  }, [userId]);

  const handleUpgrade = async () => {
    // Preview proration before confirming
    const preview = await previewChange("plan-pro", "plan-enterprise", userId);
    if (preview && confirm(`Upgrade for $${preview.immediateAmount / 100}?`)) {
      await changeSubscription({ newResource: "plan-enterprise" });
    }
  };

  return (
    <div>
      {subscription && (
        <>
          <p>Plan: {subscription.resource}</p>
          <p>Status: {subscription.status}</p>
          <button onClick={handleUpgrade}>Upgrade to Enterprise</button>
          <button onClick={() => cancelSubscription(false)}>Cancel at Period End</button>
          <button onClick={() => openBillingPortal(userId)}>Manage Billing</button>
        </>
      )}
    </div>
  );
}

Billing Intervals: weekly, monthly, yearly, custom

Subscription Features:

  • Trial periods (Stripe)
  • Proration on plan changes
  • Immediate or period-end cancellation
  • Stripe billing portal integration
  • Backend-verified subscription status for x402 gating

Coupon Codes

<CedrosPay
  resource="premium-content"
  checkout={{
    couponCode: "LAUNCH50", // Pass from user input or auto-apply
  }}
/>

Coupon Stacking Supported! Unlimited auto-apply coupons can stack with 1 manual coupon code. Percentage discounts apply first (multiplicatively), then fixed discounts are subtracted.

Two-Phase Coupon System

Coupons are applied in two phases to provide clear pricing transparency:

  1. Catalog-level coupons - Product-specific discounts shown on product pages

    • Configured with applies_at: catalog and specific product_ids
    • Example: "20% off this specific item"
    • Discounted price shown immediately when viewing the product
  2. Checkout-level coupons - Site-wide promotions applied at cart

    • Configured with applies_at: checkout and scope: all
    • Example: "10% off your entire order"
    • Applied after catalog discounts at checkout

Single Product Quote Response:

{
  "crypto": {
    "maxAmountRequired": "184000", // Actual amount to charge (atomic units)
    "extra": {
      "original_amount": "1.000000",
      "discounted_amount": "0.184000",
      "applied_coupons": "PRODUCT20,SITE10,CRYPTO5AUTO,FIXED5", // All applied
      "catalog_coupons": "PRODUCT20", // Product-specific
      "checkout_coupons": "SITE10,CRYPTO5AUTO,FIXED5", // Site-wide
      "decimals": 6
    }
  }
}

Cart Quote Response:

{
  "totalAmount": 2.7661,
  "metadata": {
    "subtotal_after_catalog": "3.820000",
    "discounted_amount": "2.766100",
    "catalog_coupons": "PRODUCT20",
    "checkout_coupons": "SITE10,CRYPTO5AUTO,FIXED5",
    "coupon_codes": "PRODUCT20,SITE10,CRYPTO5AUTO,FIXED5"
  },
  "items": [
    {
      "resource": "item-1",
      "priceAmount": 0.8, // After catalog discount
      "originalPrice": 1.0,
      "appliedCoupons": ["PRODUCT20"]
    }
  ]
}

Display Guidelines:

  • Product pages: Show strikethrough original price with catalog discount
  • Cart: Show catalog discounts on items, checkout discounts in summary
  • Always use maxAmountRequired for actual transactions - extra fields are display-only

Coupons are configured server-side with:

  • Percentage or fixed amount discounts
  • Expiration dates
  • Usage limits
  • Auto-apply functionality
  • Payment method filtering (Stripe-only, x402-only, or both)
  • Phase configuration (applies_at: catalog or checkout)

After a successful x402 payment, parse applied coupons from the settlement response:

import {
  parseCouponCodes,
  calculateDiscountPercentage,
} from "@cedros/pay-react";

// Parse applied coupons
const appliedCoupons = parseCouponCodes(settlement.metadata);
// ["SITE10", "CRYPTO5AUTO", "SAVE20"]

// Calculate total discount percentage
const discountPercent = calculateDiscountPercentage(
  parseFloat(settlement.metadata.original_amount),
  parseFloat(settlement.metadata.discounted_amount)
);

Theme Customization

<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: window.location.origin,
    solanaCluster: "mainnet-beta",
    theme: "dark", // "light" or "dark"
    themeOverrides: {
      stripeBackground: "#6366f1",
      cryptoBackground: "#0ea5e9",
      buttonBorderRadius: "12px",
    },
  }}
>
  {/* Your app */}
</CedrosProvider>

Unstyled Mode (Custom Design Systems)

For complete control over styling, use the unstyled prop to disable all default styles:

<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: window.location.origin,
    solanaCluster: "mainnet-beta",
    unstyled: true, // Disables all default CSS classes and styles
  }}
>
  <CedrosPay
    resource="item-id"
    display={{ className: "my-custom-button-class" }}
  />
</CedrosProvider>

Why use unstyled mode?

  • Build custom design systems without fighting CSS specificity
  • Use your own CSS framework (Tailwind, Material UI, etc.)
  • Full control over component appearance and behavior
  • No need to override or reset default styles

What gets disabled:

  • All cedros-theme__* CSS classes
  • Default inline styles from theme tokens
  • Button styling (stripe/crypto gradients, hover effects)
  • Error/success message styling

What you still get:

  • All payment logic and wallet integration
  • Event handlers and callbacks
  • Component structure and behavior
  • Props like className for your custom styling

Admin Integration

Cedros Pay admin UI is delivered through the shared AdminShell contract. Use @cedros/admin-react for the shell, @cedros/login-react for admin auth, and @cedros/pay-react/admin for the payment plugin.

@cedros/pay-react no longer ships a standalone admin dashboard component. Admin usage always goes through the shared Cedros admin shell.

Available Cedros Pay Sections:

| Section | Description | |---------|-------------| | transactions | Payment history with filtering by method | | orders | Order records, fulfillment history, and gift card redemption status | | products | Manage products, pricing, and variations | | subscriptions | Configure subscription plans | | coupons | Create and manage discount codes | | refunds | Process refund requests | | compliance | Review compliance checks and enforcement status | | chat-logs | Inspect AI chat sessions and message history | | customers | Search, create, and update customer records | | disputes | Review and update disputes / chargebacks | | returns | Manage return requests and statuses | | images | Upload and review storefront images | | inventory | View inventory adjustment history | | storefront | Customize shop layout and appearance | | ai-settings | Configure AI-powered features | | payment-settings | Payment method configuration | | token22 | Manage gift cards, Token-22 settings, and liquidity | | messaging | Email and notification templates | | webhooks | Monitor webhook delivery and DLQ events | | shipping | Manage shipping profiles and rates | | tax | Configure tax rates | | settings | Server and integration settings |

Required Packages

Install all three packages for admin usage:

npm install @cedros/admin-react @cedros/login-react @cedros/pay-react

Load the shared shell styles once in your admin host:

import '@cedros/admin-react/styles.css';

AdminShell Integration

For apps using both cedros-login and cedros-pay, use AdminShell from @cedros/admin-react with both plugins to create a combined admin interface:

import { AdminShell, HOST_SERVICE_IDS } from "@cedros/admin-react";
import {
  cedrosLoginPlugin,
  useCedrosLogin,
} from "@cedros/login-react/admin-only";
import { cedrosPayPlugin } from "@cedros/pay-react/admin";

function UnifiedAdmin() {
  const { user, getAccessToken, serverUrl } = useCedrosLogin();

  // Build host context from your auth providers
  const hostContext = {
    services: {
      [HOST_SERVICE_IDS.cedrosLogin]: {
        user,
        getAccessToken,
        serverUrl,
      },
      [HOST_SERVICE_IDS.cedrosPay]: {
        serverUrl: "https://api.example.com",
      },
    },
  };

  return (
    <AdminShell
      title="Admin Dashboard"
      plugins={[cedrosLoginPlugin, cedrosPayPlugin]}
      hostContext={hostContext}
      defaultSection="cedros-pay:transactions"
    />
  );
}

Note: cedros-pay no longer ships a standalone admin dashboard. Use @cedros/admin-react with cedrosPayPlugin under a single /admin dashboard.

Auth contract: In the shared shell, Cedros Login is the admin auth provider. cedros-pay consumes the host service bag exposed by cedros-login rather than standing up its own dashboard/auth runtime.

cedrosPayPlugin Sections

The plugin registers these sections for the unified dashboard:

| Section ID | Group | Description | |------------|-------|-------------| | transactions | Store | Payment transactions | | orders | Store | Orders and fulfillment details | | products | Store | Product management | | subscriptions | Store | Subscription plans | | coupons | Store | Coupon management | | refunds | Store | Refund requests | | compliance | Store | Compliance checks and review tools | | chat-logs | Store | AI chat session logs | | customers | Store | Customer records | | disputes | Store | Disputes and chargebacks | | returns | Store | Return request workflows | | images | Store | Storefront image assets | | inventory | Store | Inventory adjustment history | | storefront | Configuration | Storefront settings | | ai-settings | Configuration | AI assistant settings | | payment-settings | Configuration | Payment method toggles | | token22 | Configuration | Gift cards and Token-22 controls | | messaging | Configuration | Email/webhook settings | | webhooks | Configuration | Webhook delivery management | | shipping | Configuration | Shipping profiles and rates | | tax | Configuration | Tax rates | | settings | Configuration | Server configuration |

Section Addressing

Sections are addressed using the format pluginId:sectionId:

  • cedros-pay:transactions - Cedros Pay transactions
  • cedros-pay:products - Cedros Pay products
  • cedros-login:users - Cedros Login user management
  • cedros-login:orgs - Cedros Login organizations

See the cedros-login documentation for full AdminShell API reference and custom plugin creation.

Integration contract: For complete embedding requirements (host context, section allowlists, route prefixes, auth troubleshooting), see docs/admin-embedding-contract.md.


Props Reference

CedrosProvider Configuration

| Prop | Type | Description | | ----------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | stripePublicKey | string | Stripe publishable key (required) | | solanaCluster | 'mainnet-beta' \| 'devnet' \| 'testnet' | Solana network (required) | | serverUrl | string | Backend API URL (defaults to current origin) | | theme | 'light' \| 'dark' | Theme mode (default: 'light') | | themeOverrides | Partial<CedrosThemeTokens> | Custom theme token overrides | | unstyled | boolean | Disable all default styles (default: false) | | solanaEndpoint | string | Custom Solana RPC endpoint | | tokenMint | string | SPL token mint address (default: USDC) - see Token Mint Validation | | dangerouslyAllowUnknownMint | boolean | Allow unknown token mints (default: false) - ⚠️ WARNING: Only enable after triple-checking mint address - see Token Mint Validation | | featureFlags | Partial<Record<FeatureFlagName, boolean>> | Explicit feature flag overrides. Precedence is config.featureFlags > environment variable > registry default. | | logLevel | LogLevel | Logging verbosity (default: LogLevel.WARN in production, LogLevel.DEBUG in development) - see Logging |

Feature Flags

Cedros Pay uses a central feature flag registry in src/featureFlags.ts. Each flag has one stable positive name, a description, a default value, and a rollout stage. Call sites should read flags through the resolved helper instead of hard-coding boolean checks.

Consumer overrides

Package consumers can opt into or out of flags in provider config:

<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: "https://api.example.com",
    solanaCluster: "devnet",
    featureFlags: {
      complianceCheck: true
    }
  }}
>
  <App />
</CedrosProvider>

Environment variable overrides are also supported when your runtime or bundler exposes process.env values to the package:

CEDROS_FEATURE_COMPLIANCE_CHECK=true

Supported boolean env values are 1, true, yes, on, 0, false, no, and off.

Adding a new flag

Define the flag once in src/featureFlags.ts:

export const FEATURE_FLAG_REGISTRY = defineFeatureFlagRegistry({
  complianceCheck: {
    description: 'Enable pre-flight compliance checks before Stripe checkout.',
    default: false,
    stage: 'stable',
  },
  newParser: {
    description: 'Use the new parser implementation.',
    default: false,
    stage: 'experimental',
  },
});

Then resolve it where needed:

const enabled = isFeatureEnabled('newParser', {
  featureFlags: config.featureFlags,
});

Flipping the default later

Use positive names such as newParser, not negative names such as disableNewParser. That keeps the flag name stable across rollout:

  1. Ship it disabled by default with default: false.
  2. Let consumers opt in with featureFlags.newParser = true or CEDROS_FEATURE_NEW_PARSER=true.
  3. When the feature is ready, change only the registry entry to default: true.
  4. Consumers can still opt out temporarily with false if they need a rollback window.

Precedence is always:

  1. Explicit runtime config in featureFlags
  2. Environment variable override
  3. Registry default

The existing top-level complianceCheck config field is still accepted for backward compatibility, but new integrations should prefer featureFlags.complianceCheck.

CedrosPay Component

| Prop | Type | Description | | ----------- | ----------------- | ------------------------------------------------------------ | | resource | string | Single resource ID (use this OR items) | | items | CartItem[] | Array of cart items (use this OR resource) | | checkout | CheckoutOptions | Customer email, coupons, redirects, metadata | | display | DisplayOptions | Labels, visibility (showCard, showCrypto), layout, className | | callbacks | CallbackOptions | onPaymentSuccess, onPaymentError, onPaymentAttempt | | advanced | AdvancedOptions | Custom wallets, autoDetectWallets, testPageUrl |

Checkout Options

| Field | Type | Description | | --------------- | ------------------------ | ---------------------------------- | | customerEmail | string | Pre-fill email for Stripe checkout | | couponCode | string | Coupon code to apply | | successUrl | string | Stripe redirect URL on success. React Native may use an allowlisted app deep link or universal link. | | cancelUrl | string | Stripe redirect URL on cancel. React Native may use an allowlisted app deep link or universal link. | | metadata | Record<string, string> | Custom tracking data |

Display Options

| Field | Type | Description | | -------------- | ---------------------------- | ------------------------------------ | | cardLabel | string | Stripe button label | | cryptoLabel | string | Crypto button label | | creditsLabel | string | Credits button label | | showCard | boolean | Show Stripe button (default: true) | | showCrypto | boolean | Show crypto button (default: true) | | showCredits | boolean | Show credits button (default: false) | | layout | 'vertical' \| 'horizontal' | Button layout (default: 'vertical') | | className | string | Custom CSS class |

Callback Options

| Field | Type | Description | | ------------------ | ----------------------------------------------------- | ---------------------------- | | onPaymentSuccess | (result: PaymentSuccessResult) => void | Called on successful payment | | onPaymentError | (error: PaymentErrorDetail) => void | Called on payment error | | onPaymentAttempt | (method: 'stripe' \| 'crypto' \| 'credits') => void | Called when payment starts |

Full API Reference →


⚠️ Token Mint Validation

CRITICAL: Typos in token mint addresses result in payments being sent to the wrong token, causing permanent loss of funds.

Cedros Pay includes strict validation against known stablecoin addresses to prevent catastrophic misconfigurations. If you specify a tokenMint that doesn't match a known stablecoin, initialization will fail with an error.

Known Stablecoins (mainnet-beta)

| Symbol | Mint Address | | ------ | ---------------------------------------------- | | USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | | USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | | PYUSD | 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo | | CASH | CASHx9KJUStyftLFWGvEVf59SGeG9sh5FfcnZMVPCASH |

Strict Mode (Default)

By default, unknown token mints throw an error:

<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: window.location.origin,
    solanaCluster: "mainnet-beta",
    tokenMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"  // ✅ USDC - works
  }}
>
  {/* ... */}
</CedrosProvider>

// Using an unknown token mint throws an error:
<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: window.location.origin,
    solanaCluster: "devnet",
    tokenMint: "CustomTokenMint123..."  // ❌ Throws error: SAFETY ERROR
  }}
>
  {/* ... */}
</CedrosProvider>

Error Message:

SAFETY ERROR: Unrecognized token mint address in CedrosConfig.tokenMint
  Provided: CustomTokenMint123...

This token mint does not match any known stablecoin addresses.
Using an unknown token mint can result in PERMANENT LOSS OF FUNDS if it's a typo.

Known stablecoin mints (mainnet-beta):
  USDC: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
  USDT: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
  PYUSD: 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo
  CASH: CASHx9KJUStyftLFWGvEVf59SGeG9sh5FfcnZMVPCASH

If you are CERTAIN this is the correct mint address (custom token, testnet, or new stablecoin),
set dangerouslyAllowUnknownMint={true} in your CedrosProvider config.

Permissive Mode (Opt-in)

For custom tokens, testnet tokens, or new stablecoins, you must explicitly opt-in:

<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: window.location.origin,
    solanaCluster: "devnet",
    tokenMint: "CustomTokenMint123...", // Custom token
    dangerouslyAllowUnknownMint: true, // ⚠️ Explicit opt-in required
  }}
>
  {/* ... */}
</CedrosProvider>

⚠️ WARNING: Only enable dangerouslyAllowUnknownMint if you have TRIPLE-CHECKED the mint address. A typo will result in permanent loss of funds.

Validation Points

Strict validation runs at three points to protect against fund loss:

  1. Config initialization - When <CedrosProvider> mounts
  2. Payment quote - When backend returns x402 quote with asset field
  3. Runtime - When building Solana transactions

Best Practices:

  1. ✅ Use known stablecoin mints in production (USDC, USDT, PYUSD, CASH)
  2. ✅ Triple-check any custom mint addresses before enabling dangerouslyAllowUnknownMint
  3. ✅ Test thoroughly on devnet before deploying to mainnet
  4. ❌ Never copy-paste mint addresses without verification
  5. ❌ Never use dangerouslyAllowUnknownMint unless absolutely necessary

📊 Logging

Cedros Pay includes structured logging with configurable log levels to control verbosity and keep production logs clean.

Log Levels

import { LogLevel } from "@cedros/pay-react";

export enum LogLevel {
  DEBUG = 0, // Detailed debug information (verbose)
  INFO = 1, // Informational messages
  WARN = 2, // Warnings and potentially problematic situations
  ERROR = 3, // Error messages only
  SILENT = 4, // No logging
}

Default Behavior

  • Development: LogLevel.DEBUG (show all logs)
  • Production: LogLevel.WARN (warnings and errors only)

Configuration

Control logging verbosity via the logLevel prop:

import { CedrosProvider, LogLevel } from '@cedros/pay-react';

// Production: Only show errors
<CedrosProvider
  config={{
    stripePublicKey: "pk_live_...",
    serverUrl: window.location.origin,
    solanaCluster: "mainnet-beta",
    logLevel: LogLevel.ERROR
  }}
>
  <App />
</CedrosProvider>

// Development: Show all logs (default)
<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: window.location.origin,
    solanaCluster: "devnet",
    logLevel: LogLevel.DEBUG
  }}
>
  <App />
</CedrosProvider>

// CI/Testing: Silence all logs
<CedrosProvider
  config={{
    stripePublicKey: "pk_test_...",
    serverUrl: window.location.origin,
    solanaCluster: "devnet",
    logLevel: LogLevel.SILENT
  }}
>
  <App />
</CedrosProvider>

Advanced Usage

For custom logging or integration with your logging infrastructure:

import { createLogger, LogLevel } from "@cedros/pay-react";

// Create a custom logger instance
const logger = createLogger({
  level: LogLevel.INFO,
  prefix: "[MyApp]", // Optional prefix for all logs
});

// Use directly
logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");

// Update log level dynamically
logger.setLevel(LogLevel.ERROR);

Log Format

All logs include timestamps and severity levels:

[2025-11-09T10:43:12.345Z] [CedrosPay] [WARN] Token mint validation warning...
[2025-11-09T10:43:15.678Z] [CedrosPay] [ERROR] Payment verification failed

Best Practices

  1. Production: Use LogLevel.ERROR or LogLevel.WARN to avoid exposing sensitive data
  2. Development: Use LogLevel.DEBUG to troubleshoot payment flows
  3. CI/Testing: Use LogLevel.SILENT to keep test output clean
  4. Monitoring: Integrate with your logging infrastructure (Datadog, Sentry, etc.)

🪄 Example Use Cases

  • Paywalled blog or API monetization
  • Agent-to-agent microtransactions
  • Subscription and one-time digital content unlocks
  • AI service pay-per-call endpoints

Semantic Versioning

We follow Semantic Versioning:

  • Major (x.0.0): Breaking changes, API removals
  • Minor (0.x.0): New features, backwards-compatible additions
  • Patch (0.0.x): Bug fixes, no API changes

Stable API Surface

These exports are guaranteed stable and follow semantic versioning:

  • Components - All exported React components (CedrosPay, StripeButton, CryptoButton, CreditsButton, SubscribeButton, CryptoSubscribeButton, CreditsSubscribeButton, SubscriptionManagementPanel, etc.)
  • Hooks - useCedrosContext, useStripeCheckout, useX402Payment, useCreditsPayment, useSubscription, useCryptoSubscription, useCreditsSubscription, useSubscriptionManagement, etc.
  • Manager Interfaces - IStripeManager, IX402Manager, IWalletManager, ISubscriptionManager, ISubscriptionChangeManager, IRouteDiscoveryManager
  • Types - All types exported via versioned namespaces (v1, v2, etc.)
  • Utilities - validateConfig, parseCouponCodes, rate limiters, logging, events

Use interfaces, not concrete classes:

// ✅ CORRECT: Use interface from context
import { useCedrosContext } from '@cedros/pay-react';

function MyComponent() {
  const { stripeManager } = useCedrosContext();
  // stripeManager is typed as IStripeManager (stable)
  await stripeManager.processPayment({ ... });
}

// ❌ WRONG: Direct class import (unsupported)
import { StripeManager } from '@cedros/pay-react'; // Not exported
const manager = new StripeManager(...); // Will break

Deprecation Process

When APIs are deprecated:

  1. Deprecation Notice - Warning logged, replacement documented
  2. Minimum 3 months - Grace period for migration
  3. Migration Guide - Step-by-step upgrade instructions
  4. Major Version - Removal in next major release only

Example Timeline:

  • v2.1.0: Deprecate oldAPI, introduce newAPI
  • v2.2.0 - v2.x: Both supported, warnings logged
  • v3.0.0: Remove oldAPI, only newAPI available

Type Versioning

Types use versioned namespaces to prevent breaking changes:

// Top-level exports (current stable version)
import { X402Requirement } from '@cedros/pay-react';

// Explicit version (locks to v1, won't break on v2)
import { v1 } from '@cedros/pay-react';
const req: v1.X402Requirement = { ... };

// Future version
import { v2 } from '@cedros/pay-react';
const newReq: v2.X402Requirement = { ... };

Read more: See API_STABILITY.md for our complete stability policy.


Error Telemetry (Optional)

Cedros Pay includes opt-in error telemetry with correlation IDs for production debugging. Telemetry is disabled by default and requires explicit configuration.

Privacy-First Design

  • Opt-in only - No data sent without your explicit configuration
  • User-controlled - You choose what service to use (Sentry, Datadog, custom, or none)
  • PII sanitization - Private keys, wallet addresses, emails automatically redacted
  • No hidden network calls - Data only sent via your callback function

Quick Start

import { configureTelemetry, ErrorSeverity } from "@cedros/pay-react";
import * as Sentry from "@sentry/react";

// Enable telemetry with Sentry
configureTelemetry({
  enabled: true,
  sdkVersion: "2.0.0",
  environment: process.env.NODE_ENV,
  sanitizePII: true, // ALWAYS keep enabled
  onError: (error) => {
    Sentry.captureException(error.error, {
      extra: {
        correlationId: error.correlationId,
        paymentContext: error.paymentContext,
      },
      tags: error.tags,
      level: error.severity,
    });
  },
});

Features