@superlogic/spree-pay
v0.4.14
Published
Spree-pay React component and utilities
Keywords
Readme
@superlogic/spree-pay
Publishable React package exposing the SpreePay component and related utilities.
Install
npm i @superlogic/spree-payUsage
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 + feein 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>