Reusable react components for payment kit v2
npm install @blocklet/payment-react
A React component library for building payment flows, subscriptions, and donation systems in blocklets, seamlessly integrated with Payment Kit.
- ๐ ๏ธ Pre-built UI Components: Includes checkout forms, pricing tables, donation widgets, and more
- ๐จ Customizable Themes: Full control over styling via Material-UI themes
- ๐ i18n Support: Built-in localization for global audiences
- ๐งฉ Lazy Loading: Optimize bundle size with dynamic imports
- ๐ณ Payment Operations: Handle subscriptions, refunds, invoices, and metered billing
- Payment Kit Documentation - Official documentation with detailed guides and API references
``bash`
npm install @blocklet/payment-react
`tsx
import { PaymentProvider, CheckoutForm } from '@blocklet/payment-react';
function App() {
return (
mode="inline" // Embed directly in your UI
showCheckoutSummary={true}
onChange={(state) => console.log('Checkout State:', state)}
/>
);
}
`
- Payment form for checkout sessions and payment links
- CheckoutTable - Pricing table display
- CheckoutDonate - Donation widget
- OverdueInvoicePayment - Handle overdue invoice payments
- AutoTopup - Auto-recharge configuration display card
- AutoTopupModal - Auto-recharge configuration modal$3
- FormInput - Base form input component
- PhoneInput - Phone number input with validation
- AddressForm - Complete address form
- StripeForm - Stripe payment form
- CurrencySelector - Currency selection dropdown
- CountrySelect - Country selection dropdown$3
- Status - Status indicator
- Livemode - Test mode indicator
- Switch - Toggle switch
- ConfirmDialog - Confirmation dialog
- Amount - Amount display with formatting
- TruncatedText - Text truncation
- Link - Safe navigation link$3
`tsx
// Loading Button with state management
import { LoadingButton } from '@blocklet/payment-react';function PaymentButton() {
const [loading, setLoading] = useState(false);
const handlePayment = async () => {
setLoading(true);
try {
await processPayment();
} finally {
setLoading(false);
}
};
return (
loading={loading}
onClick={handlePayment}
variant="contained"
color="primary"
>
Pay Now
);
}
`$3
- TxLink - Transaction link
- TxGas - Gas fee display
- PaymentBeneficiaries - Payment beneficiaries list$3
- CustomerInvoiceList - Invoice history list
- CustomerPaymentList - Payment history list$3
- PaymentProvider - Payment context provider
- DonateProvider - Donation context provider
- PaymentThemeProvider - Theme provider$3
- useSubscription - event socket callback
- useMobile - Mobile detection$3
#### API Client
`tsx
import { api } from '@blocklet/payment-react';// Basic usage
const response = await api.get('/api/payments');
const data = await api.post('/api/checkout', { amount: 100 });
// With query parameters
const results = await api.get('/api/invoices', {
params: { status: 'paid' }
});
// With request config
const config = {
headers: { 'Custom-Header': 'value' }
};
const response = await api.put('/api/subscription', data, config);
`#### Cached Request
`tsx
import { CachedRequest } from '@blocklet/payment-react';// Create a cached request
const priceRequest = new CachedRequest(
'product-prices',
() => api.get('/api/prices'),
{
strategy: 'session', // 'session' | 'local' | 'memory'
ttl: 5 60 1000 // Cache for 5 minutes
}
);
// Use the cached request
async function fetchPrices() {
// Will use cache if available and not expired
const prices = await priceRequest.fetch();
// Force refresh cache
const freshPrices = await priceRequest.fetch(true);
return prices;
}
`#### Date Handling
`tsx
import { dayjs } from '@blocklet/payment-react';// Format dates
const formatted = dayjs().format('YYYY-MM-DD');
// Parse timestamps
const date = dayjs(timestamp);
const unix = date.unix();
// Relative time
const relative = dayjs().from(date);
`#### i18n Setup
`tsx
// use your own translator
import { createTranslator } from '@blocklet/payment-react';const translator = createTranslator({
en: {
checkout: { title: 'Complete Payment' }
},
zh: {
checkout: { title: 'ๅฎๆๆฏไป' }
}
});
// use payment-react locales
import { translations as extraTranslations } from '@blocklet/payment-react';
import merge from 'lodash/merge';
import en from './en';
import zh from './zh';
export const translations = merge(
{
zh,
en,
},
extraTranslations
);
`#### Lazy Loading
`tsx
import { createLazyComponent } from '@blocklet/payment-react';const LazyComponent = createLazyComponent(async () => {
const [{ Component }, { useHook }] = await Promise.all([
import('./Component'),
import('./hooks')
]);
globalThis.__DEPENDENCIES__ = { useHook };
return Component;
});
`$3
The auto-topup feature allows users to automatically recharge their credit balance when it falls below a specified threshold. This helps ensure uninterrupted service usage.
#### AutoTopup
Display and manage auto-recharge configurations with different rendering modes.
`tsx
import { AutoTopup, PaymentProvider } from '@blocklet/payment-react';function CreditManagementPage() {
return (
{/ Default mode - fully expanded display /}
currencyId="credit-currency-id"
onConfigChange={(config) => {
console.log('Auto-topup config updated:', config);
}}
/>
{/ Simple mode - collapsed by default, expandable /}
currencyId="credit-currency-id"
mode="simple"
onConfigChange={(config) => {
// Handle configuration changes
refreshCreditBalance();
}}
/>
{/ Custom mode - full control over rendering /}
currencyId="credit-currency-id"
mode="custom"
onConfigChange={(config) => console.log('Config updated:', config)}
>
{(openModal, config, paymentData, loading) => (
{loading ? (
Loading auto-topup configuration...
) : (
Auto Recharge Status
Status: {config?.enabled ? 'Active' : 'Inactive'}
{config?.enabled && (
Threshold: {config.threshold} {config.currency?.symbol}
)}
{paymentData?.balanceInfo && (
Wallet Balance: {paymentData.balanceInfo.token} {config?.rechargeCurrency?.symbol}
)}
)}
)}
);
}
`#### AutoTopupModal
Configure auto-recharge settings including threshold, payment method, and purchase amount.
`tsx
import { AutoTopupModal, PaymentProvider } from '@blocklet/payment-react';
import { useState } from 'react';function AutoRechargeSettings({ currencyId }) {
const [modalOpen, setModalOpen] = useState(false);
const handleSuccess = (config) => {
console.log('Auto-recharge configured:', config);
setModalOpen(false);
// Refresh the parent component or update state
};
const handleError = (error) => {
console.error('Configuration failed:', error);
};
return (
open={modalOpen}
onClose={() => setModalOpen(false)}
currencyId={currencyId}
onSuccess={handleSuccess}
onError={handleError}
defaultEnabled={true} // Start with auto-recharge enabled
/>
);
}
`#### Component Props
AutoTopup Props:
-
currencyId [Required] - The currency ID for auto-recharge
- onConfigChange [Optional] - Callback when configuration changes: (config: AutoRechargeConfig) => void
- mode [Optional] - Rendering mode: 'default' | 'simple' | 'custom'
- sx [Optional] - Custom styles
- children [Optional] - Custom render function for custom mode: (openModal, config, paymentData, loading) => ReactNodeAutoTopupModal Props:
-
open [Required] - Whether modal is open
- onClose [Required] - Close modal callback
- currencyId [Required] - The currency ID for auto-recharge
- customerId [Optional] - Customer ID (defaults to current session user)
- onSuccess [Optional] - Success callback: (config: AutoRechargeConfig) => void
- onError [Optional] - Error callback: (error: any) => void
- defaultEnabled [Optional] - Whether to default the enabled state to true
Complete Examples
$3
`tsx
import {
DonateProvider,
CheckoutDonate,
PaymentProvider
} from '@blocklet/payment-react';
import { useEffect, useState } from 'react';function DonationPage() {
const [session, setSession] = useState(null);
useEffect(() => {
// Get session from your auth system
const getSession = async () => {
const userSession = await fetchSession();
setSession(userSession);
};
getSession();
}, []);
return (
mountLocation="your-unique-donate-instance"
description="Help locate this donation instance"
defaultSettings={{
btnText: 'Like',
}}
>
settings={{
target: "post-123", // required, unique identifier for the donation instance
title: "Support Author", // required, title of the donation modal
description: "If you find this article helpful, feel free to buy me a coffee", // required, description of the donation
reference: "https://your-site.com/posts/123", // required, reference link of the donation
beneficiaries: [
{
address: "tip user did", // required, address of the beneficiary
share: "100", // required, percentage share
},
],
}}
/>
{/ Custom donation history display /}
mode="custom"
settings={{
target: "post-123", // required, unique identifier for the donation instance
title: "Support Author", // required, title of the donation modal
description: "If you find this article helpful, feel free to buy me a coffee", // required, description of the donation
reference: "https://your-site.com/posts/123", // required, reference link of the donation
beneficiaries: [
{
address: "tip user did", // required, address of the beneficiary
share: "100", // required, percentage share
},
],
}}
>
{(openDonate, totalAmount, supporters, loading, settings) => (
Our Supporters
{loading ? (
) : (
Total Donations: {totalAmount} {supporters.currency?.symbol}
{supporters.supporters.map(supporter => (
{supporter.customer?.name}
{supporter.amount_total} {supporters.currency?.symbol}
))}
)}
)}
);
}
`$3
Complete example showing how to integrate auto-topup functionality into a credit management dashboard.
`tsx
import {
PaymentProvider,
AutoTopup,
AutoTopupModal,
CustomerInvoiceList,
Amount
} from '@blocklet/payment-react';
import { useState, useEffect } from 'react';
import { Grid, Card, CardContent, Typography, Button } from '@mui/material';function CreditDashboard() {
const [session, setSession] = useState(null);
const [creditCurrencies, setCreditCurrencies] = useState([]);
const [showSetupModal, setShowSetupModal] = useState(false);
const [selectedCurrency, setSelectedCurrency] = useState(null);
useEffect(() => {
// Initialize session and fetch credit currencies
const initializeDashboard = async () => {
const userSession = await fetchSession();
setSession(userSession);
const currencies = await fetchCreditCurrencies();
setCreditCurrencies(currencies);
};
initializeDashboard();
}, []);
const handleAutoTopupChange = (currencyId, config) => {
console.log(
Auto-topup updated for ${currencyId}:, config);
// Update local state or refetch data
}; return (
{/ Credit Balance Cards with Auto-Topup /}
{creditCurrencies.map((currency) => (
{currency.name} Balance
{/ Current balance display /}
amount={currency.balance}
decimal={currency.decimal}
symbol={currency.symbol}
/>
{/ Auto-topup configuration /}
currencyId={currency.id}
mode="simple"
onConfigChange={(config) =>
handleAutoTopupChange(currency.id, config)
}
sx={{ mt: 2 }}
/>
{/ Manual setup button for currencies without auto-topup /}
variant="outlined"
onClick={() => {
setSelectedCurrency(currency);
setShowSetupModal(true);
}}
sx={{ mt: 1 }}
>
Configure Auto-Recharge
))}
{/ Usage History /}
Credit Usage History
customer_id={session?.user?.did}
type="table"
include_staking
status="paid,open"
/>
{/ Auto-topup Setup Modal /}
{showSetupModal && selectedCurrency && (
open={showSetupModal}
onClose={() => {
setShowSetupModal(false);
setSelectedCurrency(null);
}}
currencyId={selectedCurrency.id}
onSuccess={(config) => {
console.log('Auto-topup configured:', config);
handleAutoTopupChange(selectedCurrency.id, config);
setShowSetupModal(false);
setSelectedCurrency(null);
}}
onError={(error) => {
console.error('Auto-topup setup failed:', error);
}}
defaultEnabled={true}
/>
)}
);
}
// Custom auto-topup display using custom mode
function CustomAutoTopupDisplay({ currencyId }) {
return (
currencyId={currencyId}
mode="custom"
>
{(openModal, config, paymentData, loading) => {
if (loading) return
Loading...; return (
Smart Auto-Recharge
{config?.enabled ? (
โ Active - Recharges when balance drops below {config.threshold} {config.currency?.symbol}
Next recharge: {config.quantity}x {config.price?.product?.name}
{paymentData?.balanceInfo && (
Wallet Balance: {paymentData.balanceInfo.token} {config.rechargeCurrency?.symbol}
)}
) : (
Auto-recharge is not configured
)}
variant="contained"
size="small"
onClick={openModal}
sx={{ mt: 2 }}
>
{config?.enabled ? 'Modify Settings' : 'Setup Auto-Recharge'}
);
}}
);
}
`$3
-
ResumeSubscription component
- Resume subscription, with support for re-stake if needed
- Props:
- subscriptionId: [Required] The subscription ID to resume
- onResumed: [Optional] Callback function called after successful resume, receives (subscription)
- dialogProps: [Optional] Dialog properties, default is { open: true }
- successToast: [Optional] Whether to show success toast, default is true
- authToken: [Optional] Authentication token for API requests`tsx
import {
PaymentProvider,
ResumeSubscription,
CustomerInvoiceList,
Amount
} from '@blocklet/payment-react';function SubscriptionPage({ subscriptionId }) {
return (
subscriptionId={subscriptionId}
onResumed={(subscription) => {
// Refresh subscription status
refetchSubscription();
}}
/>
{/ Custom dialog props /}
subscriptionId={subscriptionId}
dialogProps={{
open: true,
title: 'Resume Your Subscription',
onClose: () => {
// Handle dialog close
}
}}
/>
{/ With auth token /}
subscriptionId={subscriptionId}
authToken="your-auth-token"
/>
);
}
`-
OverdueInvoicePayment component
- Display overdue invoices for a subscription, and support batch payment
- Props:
- subscriptionId: [Optional] The subscription ID
- customerId: [Optional] The customer ID or DID
- onPaid: [Optional] Callback function called after successful payment, receives (id, currencyId, type)
- mode: [Optional] Component mode, default or custom (default is default)
- dialogProps: [Optional] Dialog properties, default is { open: true }
- detailLinkOptions: [Optional] Detail link options, format: { enabled, onClick, title }
- successToast: [Optional] Whether to show success toast, default is true
- children: [Optional] Custom rendering function, used only when mode="custom" - Custom Mode:
-
children function receives two parameters:
- handlePay: Function to start the payment process
- data: Payment data (includes subscription, summary, invoices, subscriptionCount, detailUrl)
`tsx
import {
PaymentProvider,
OverdueInvoicePayment,
CustomerInvoiceList,
Amount
} from '@blocklet/payment-react';function SubscriptionPage({ subscriptionId }) {
return (
{/ Handle subscription overdue payments /}
subscriptionId={subscriptionId}
onPaid={() => {
// Refresh subscription status
refetchSubscription();
}}
/>
{/ Handle customer overdue payment /}
customerId={session.user.did}
onPaid={() => {
// Refresh customer status
refetch();
}}
/>
{/ Custom Overdue Invoice Payment /}
subscriptionId={subscriptionId}
onPaid={() => {
refetchSubscription();
}}
mode="custom"
>
{(handlePay, { subscription, summary, invoices }) => (
{Object.entries(summary).map(([currencyId, info]) => (
Due Amount:
amount={info.amount}
/>
{info.currency?.symbol}
onClick={() => handlePay(info)}
variant="contained"
>
Pay Now
))}
)}
{/ Display invoice history /}
subscription_id={subscriptionId}
type="table"
include_staking
status="open,paid,uncollectible,void"
/>
);
}
`Best Practices
$3
`tsx
// 1. Choose appropriate cache strategy
const shortLivedCache = new CachedRequest('key', fetchData, {
strategy: 'memory',
ttl: 60 * 1000 // 1 minute
});const persistentCache = new CachedRequest('key', fetchData, {
strategy: 'local',
ttl: 24 60 60 * 1000 // 1 day
});
// 2. Clear cache when data changes
async function updateData() {
await api.post('/api/data', newData);
await cache.fetch(true); // Force refresh
}
// 3. Handle cache errors
try {
const data = await cache.fetch();
} catch (err) {
console.error('Cache error:', err);
// Fallback to fresh data
const fresh = await cache.fetch(true);
}
`$3
- Use lazy loading for non-critical components
- Import only required components
- Leverage code splitting with dynamic imports$3
- Maintain consistent styling across components
- Use theme provider for global style changes
- Override styles at component level when needed$3
Since version 1.14.22, the component includes a built-in theme provider. If you need to modify the styles of internal components, pass the
theme property to override or inherit the external theme.| Option | Description |
| --- | --- |
| default | Wrapped with built-in
PaymentThemeProvider |
| inherit | Use the parent component's themeProvider |
| PaymentThemeOptions | Override some styles of PaymentThemeProvider |`tsx
// 1. Use themeOptions
id="plink_xxx"
onChange={console.info}
theme={{
components: {
MuiButton: {
styleOverrides: {
containedPrimary: {
backgroundColor: '#1DC1C7',
color: '#fff',
'&:hover': {
backgroundColor: 'rgb(20, 135, 139)',
},
},
},
},
},
}}
/>// 2. Use theme sx
id="plink_xxx"
showCheckoutSummary={false}
onChange={console.info}
theme={{
sx: {
'.cko-submit-button': {
backgroundColor: '#1DC1C7',
color: '#fff',
'&:hover': {
backgroundColor: 'rgb(20, 135, 139)',
},
},
},
}}
/>
`$3
``tsx// Status indicator for payment states
color="success"
size="small"
sx={{ margin: 1 }}
/>
// Test mode indicator
// Custom switch button
onChange={(checked) => console.log('Switched:', checked)}
/>
// Safe navigation link
Apache-2.0