A React component library for building payment flows, subscriptions, and donation systems in Blocklet applications. Seamlessly integrated with Blocklet's payment infrastructure.
- 🛠️ 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
- Example Implementation - Complete example showing how to integrate the payment components
npm install @blocklet/payment-react
import { PaymentProvider, CheckoutForm } from '@blocklet/payment-react';
function App() {
return (
<PaymentProvider session={session} connect={connectApi}>
<CheckoutForm
id="plink_xxx" // Payment Link ID
mode="inline" // Embed directly in your UI
showCheckoutSummary={true}
onChange={(state) => console.log('Checkout State:', state)}
/>
</PaymentProvider>
);
}
-
CheckoutForm
- Payment form for checkout sessions and payment links -
CheckoutTable
- Pricing table display -
CheckoutDonate
- Donation widget -
OverdueInvoicePayment
- Handle overdue invoice payments
-
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
-
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
// 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 (
<LoadingButton
loading={loading}
onClick={handlePayment}
variant="contained"
color="primary"
>
Pay Now
</LoadingButton>
);
}
-
TxLink
- Transaction link -
TxGas
- Gas fee display -
PaymentBeneficiaries
- Payment beneficiaries list
-
CustomerInvoiceList
- Invoice history list -
CustomerPaymentList
- Payment history list
-
PaymentProvider
- Payment context provider -
DonateProvider
- Donation context provider -
PaymentThemeProvider
- Theme provider
-
useSubscription
- event socket callback -
useMobile
- Mobile detection
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);
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;
}
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);
// 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
);
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;
});
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 (
<PaymentProvider session={session} connect={connectApi}>
<DonateProvider
mountLocation="your-unique-donate-instance"
description="Help locate this donation instance"
defaultSettings={{
btnText: 'Like',
}}
>
<CheckoutDonate
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 */}
<CheckoutDonate
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) => (
<div>
<h2>Our Supporters</h2>
{loading ? (
<CircularProgress />
) : (
<div>
<div>
Total Donations: {totalAmount} {supporters.currency?.symbol}
</div>
<div>
{supporters.supporters.map(supporter => (
<div key={supporter.id}>
<span>{supporter.customer?.name}</span>
<span>{supporter.amount_total} {supporters.currency?.symbol}</span>
</div>
))}
</div>
</div>
)}
</div>
)}
</CheckoutDonate>
</DonateProvider>
</PaymentProvider>
);
}
-
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 istrue
-
authToken
: [Optional] Authentication token for API requests
-
import {
PaymentProvider,
ResumeSubscription,
CustomerInvoiceList,
Amount
} from '@blocklet/payment-react';
function SubscriptionPage({ subscriptionId }) {
return (
<PaymentProvider session={session}>
<ResumeSubscription
subscriptionId={subscriptionId}
onResumed={(subscription) => {
// Refresh subscription status
refetchSubscription();
}}
/>
{/* Custom dialog props */}
<ResumeSubscription
subscriptionId={subscriptionId}
dialogProps={{
open: true,
title: 'Resume Your Subscription',
onClose: () => {
// Handle dialog close
}
}}
/>
{/* With auth token */}
<ResumeSubscription
subscriptionId={subscriptionId}
authToken="your-auth-token"
/>
</PaymentProvider>
);
}
-
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
orcustom
(default isdefault
) -
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 istrue
-
children
: [Optional] Custom rendering function, used only whenmode="custom"
-
-
Custom Mode:
-
children
function receives two parameters:-
handlePay
: Function to start the payment process -
data
: Payment data (includessubscription
,summary
,invoices
,subscriptionCount
,detailUrl
)
-
-
-
import {
PaymentProvider,
OverdueInvoicePayment,
CustomerInvoiceList,
Amount
} from '@blocklet/payment-react';
function SubscriptionPage({ subscriptionId }) {
return (
<PaymentProvider session={session}>
{/* Handle subscription overdue payments */}
<OverdueInvoicePayment
subscriptionId={subscriptionId}
onPaid={() => {
// Refresh subscription status
refetchSubscription();
}}
/>
{/* Handle customer overdue payment */}
<OverdueInvoicePayment
customerId={session.user.did}
onPaid={() => {
// Refresh customer status
refetch();
}}
/>
{/* Custom Overdue Invoice Payment */}
<OverdueInvoicePayment
subscriptionId={subscriptionId}
onPaid={() => {
refetchSubscription();
}}
mode="custom"
>
{(handlePay, { subscription, summary, invoices }) => (
<Card>
<CardHeader title="Overdue Payments" />
<CardContent>
<Stack spacing={2}>
{Object.entries(summary).map(([currencyId, info]) => (
<div key={currencyId}>
<Typography>
Due Amount:
<Amount
amount={info.amount}
/>
{info.currency?.symbol}
</Typography>
<Button
onClick={() => handlePay(info)}
variant="contained"
>
Pay Now
</Button>
</div>
))}
</Stack>
</CardContent>
</Card>
)}
</OverdueInvoicePayment>
{/* Display invoice history */}
<CustomerInvoiceList
subscription_id={subscriptionId}
type="table"
include_staking
status="open,paid,uncollectible,void"
/>
</PaymentProvider>
);
}
// 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);
}
- Use lazy loading for non-critical components
- Import only required components
- Leverage code splitting with dynamic imports
- Maintain consistent styling across components
- Use theme provider for global style changes
- Override styles at component level when needed
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
|
// 1. Use themeOptions
<CheckoutForm
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
<CheckoutForm
id="plink_xxx"
showCheckoutSummary={false}
onChange={console.info}
theme={{
sx: {
'.cko-submit-button': {
backgroundColor: '#1DC1C7',
color: '#fff',
'&:hover': {
backgroundColor: 'rgb(20, 135, 139)',
},
},
},
}}
/>
import {
Status,
Livemode,
Switch,
Link,
Amount
} from '@blocklet/payment-react';
// Status indicator for payment states
<Status
label="active"
color="success"
size="small"
sx={{ margin: 1 }}
/>
// Test mode indicator
<Livemode />
// Custom switch button
<Switch
checked={true}
onChange={(checked) => console.log('Switched:', checked)}
/>
// Safe navigation link
<Link to="/demo" />
## License
Apache-2.0