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

@superlogic/spree-pay

v0.4.14

Published

Spree-pay React component and utilities

Readme

@superlogic/spree-pay

Publishable React package exposing the SpreePay component and related utilities.

Install

npm i @superlogic/spree-pay

Usage

import { useState } from 'react';

import { SpreePay, SpreePayProvider, useSpreePay } from '@superlogic/spree-pay';
import { useCapture3DS } from '@superlogic/spree-pay';
import '@superlogic/spree-pay/styles.css';

const spreeEnv = {
  tenantId: 'your-tenant-id', // Tenant identifier provided during onboarding
  environment: 'dev' as const,
  redirect3dsURI: '/3ds', // Any valid URI in your app that captures 3ds redirect
  ssoPageURI: '/silent-check-sso.html', // Any valid URI in your app that will handle Keycloak SSO (example below)
};

function Checkout() {
  return (
    <SpreePayProvider env={spreeEnv}>
      <CheckoutContent />
    </SpreePayProvider>
  );
}

function CheckoutContent() {
  const { process, selectedPaymentMethod, isProcessing, enabled } = useSpreePay();
  const [status, setStatus] = useState<'idle' | 'processing' | 'success' | 'error'>('idle');

  async function handleProcess() {
    if (!enabled) {
      console.log('Please select a payment method');
      return;
    }

    setStatus('processing');
    try {
      // Example: create order and process payment
      const order = await getOrderHash();
      const result = await process({
        hash: order.hash,
        capture: true, // Optional: auto-capture payment
        metadata: { orderId: order.id }, // Optional: additional data
      });

      console.log('Payment result:', result);
      // result.status: AUTHORIZED or CAPTURED
      // result.paymentId: unique payment identifier
      // result.paymentType: CREDIT_CARD, CRYPTO, CDC, etc.

      setStatus('success');
    } catch (err) {
      console.error('Payment failed:', err);
      setStatus('error');
    }
  }

  return (
    <div>
      <SpreePay
        amount={99}
        onProcess={handleProcess}
        transactionFeePercentage={0.04}
        className="w-full max-w-[540px]"
        isProcessing={status === 'processing'}
        disabledPoints={false}
        topUpLink="/top-up"
      />
      {status === 'success' && <p>Payment successful! Thank you for your order.</p>}
      {status === 'error' && <p>Payment failed. Please try again.</p>}
    </div>
  );
}

// example 3DS redirect page hosted on /3ds
export default function ThreeDSRedirectPage() {
  // take payment intent from search params as payment_intent
  const searchParams = exampleGetSearchParams();

  // Pass the entire search params object to the hook
  useCapture3DS({ paymentIntent: searchParams['payment_intent'] });

  // Render null or any other UI/Loader
  return null;
}

SpreePay props

type SpreePayProps = {
  amount?: number; // USD amount to charge (e.g., 99 for $99). Used for display and fee calculation.
  onProcess?: () => Promise<void>; // Called when the Checkout button is clicked. Implement your order flow here and call useSpreePay().process({ hash, ... }).
  isProcessing?: boolean; // Shows a loading state and disables interactions when true (useful while your onProcess runs).
  transactionFeePercentage?: number; // Fee multiplier applied to the USD portion (e.g., 0.04 for 4%).
  disabledPoints?: boolean; // Disables points payment option if true.
  topUpLink?: string; // Optional link to top-up page for points balance.
  className?: string; // Optional wrapper class for layout/styling.

  // Multi-currency display (all three fields should be supplied together)
  currencyCode?: string; // ISO 4217 display currency (e.g. "AUD").
  foreignCurrencyAmount?: number; // Pre-fee order total in the display currency (e.g. 1280.46). Used directly as Pay button base; transaction fee is added on top.
  exchangeRate?: number; // 1 unit of currencyCode in USD (e.g. 0.65 for AUD→USD). Used to convert USD remainder when points are applied.
  origin?: string; // Optional source identifier (e.g. ONE_PLATFORM)
};

Multi-currency display

When currencyCode, foreignCurrencyAmount, and exchangeRate are all provided the widget shows prices in the shopper-selected currency. Settlement and points calculations continue in USD.

<SpreePay
  amount={832.3} // USD amount — used for points math and payment-intent creation
  currencyCode="AUD"
  foreignCurrencyAmount={1280.46} // pre-fee order total in AUD from your backend
  exchangeRate={0.65} // 1 AUD = 0.65 USD
  onProcess={handleProcess}
  transactionFeePercentage={0.04}
  className="w-full max-w-[540px]"
  isProcessing={status === 'processing'}
/>

Display behaviour:

  • Pay button (no points) — shows foreignCurrencyAmount + fee in the display currency
  • Pay button (split) — shows the card portion converted from USD remainder + fee
  • Points slider — card amount updates live in the display currency as the slider moves
  • Available points value — shown in display currency alongside the points count

After the user commits the points slider, selectedPaymentMethod exposes:

  • amount — USD card amount including fee (use for payment-intent creation)
  • foreignCurrencyAmount — card amount in display currency, 2 d.p. (use for order display / receipts)

If any of the three display params are absent the widget falls back to USD-only display — no breaking change.

API Reference

SpreePayProvider Props

type ENV = {
  environment: 'dev' | 'stg' | 'prod'; // Environment for API endpoints and logging
  tenantId: string; // Tenant identifier
  ssoPageURI: string; // Path to Keycloak SSO page (e.g., '/silent-check-sso.html')
  redirect3dsURI: string; // Path to 3DS redirect handler (e.g., '/3ds')
  accessToken?: string; // Optional: provide pre-authenticated token to skip Keycloak SSO
  useWeb3Points?: boolean; // Optional: enable Web3-based points (default: true)
  keycloakClientId?: string; // Optional: custom Keycloak client ID
};

useSpreePay Hook

const {
  process, // Function to trigger payment processing
  isProcessing, // Internal processing state (true while payment is being processed)
  enabled, // true if a payment method is selected and ready
  selectedPaymentMethod, // Currently selected payment method details
} = useSpreePay();

// process function signature:
async function process(params: ProcessFnParams): Promise<PaymentMethodResult>;

type ProcessFnParams = {
  hash: string; // Order hash from your backend
  capture?: boolean; // Optional: auto-capture payment (default: false)
  metadata?: Record<string, unknown>; // Optional: additional metadata to pass through
};

type PaymentMethodResult = {
  status: SlapiPaymentStatus; // Payment status (AUTHORIZED, CAPTURED, FAILED, etc.)
  paymentId: string; // Unique payment identifier
  txHash: string | null; // Transaction hash (for crypto payments)
  txId: string | null; // Transaction ID
  paymentType: PaymentType; // Type of payment used
};

Payment Types & Statuses

enum PaymentType {
  CREDIT_CARD = 'CREDIT_CARD',
  CRYPTO = 'CRYPTO',
  CDC = 'CDC', // Crypto.com Pay
  CREDIT_CARD_SPLIT = 'SPLIT', // Split payment (card + points)
  POINTS = 'POINTS',
}

enum SlapiPaymentStatus {
  AUTHORIZED = 'AUTHORIZED', // Payment authorized (success)
  CAPTURED = 'CAPTURED', // Payment captured (success)
  FAILED = 'FAILED', // Payment failed
  PENDING = 'PENDING', // Payment pending (backend only)
  NOT_INITIALIZED = 'NOT_INITIALIZED', // Not yet started (backend only)
  VOIDED = 'VOIDED', // Payment voided (backend only)
  CAPTURE_FAILED = 'CAPTURE_FAILED', // Capture failed (backend only)
}

useCapture3DS Hook

// Use in your 3DS redirect page to capture payment intent and close the modal
useCapture3DS(searchParams: Record<string, string | null>);

Logger API

import { LogLevel, configureLogger, logger } from '@superlogic/spree-pay';

// Configure logging behavior
configureLogger({
  environment: 'dev', // 'prod' = ERROR only, 'dev'/'stg' = all logs
  minLevel: LogLevel.DEBUG, // Optional: override default log level
});

// Use the logger
logger.debug('Debug message', { context: 'data' });
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message', error);

// Create child logger with prefix
const componentLogger = logger.child('MyComponent');
componentLogger.info('Component initialized');

enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3,
  NONE = 4,
}

Theme Customization

SpreePay uses CSS variables for theming (see all 58+ variables in packages/spree-pay/src/styles/globals.css:22-82). You can override these using CSS or CSS-in-JS.

Important: To override the theme locally (scoped), you need more specific CSS than just .sl-spreepay. Use a wrapper class like .your-class .sl-spreepay or combine with SpreePay's className prop.

Global override (affects all SpreePay instances)

.sl-spreepay {
  --primary: #272e32;
  --accent: #439ef4;
  --s-default: #ffffff;
}

Local/scoped override

Option A: Wrap SpreePay in a container with your own class:

<div className="my-checkout">
  <SpreePay {...props} />
</div>

<style>{`
  .my-checkout .sl-spreepay {
    --primary: #your-color;
    --accent: #your-accent;
  }
`}</style>

Option B: Pass className to SpreePay and use combined selector:

<SpreePay className="custom-theme" {...props} />

<style>{`
  .sl-spreepay.custom-theme {
    --primary: #your-color;
    --accent: #your-accent;
  }
`}</style>

Keycloak SSO page example (/silent-check-sso.html)

<!doctype html>
<html>
  <body>
    <script>
      try {
        parent.postMessage(location.href, location.origin);
      } catch (e) {}
    </script>
  </body>
</html>