@blocklet/payment-react
TypeScript icon, indicating that this package has built-in type declarations

1.20.3 • Public • Published

@blocklet/payment-react

npm version

A React component library for building payment flows, subscriptions, and donation systems in blocklets, seamlessly integrated with Payment Kit.

Features

  • 🛠️ 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

Related Links

Installation

npm install @blocklet/payment-react 

Quick Start

Basic Integration

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>
  );
}

Available Components & Utilities

Core Components

  • CheckoutForm - 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

Form Components

  • 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

Display Components

  • 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

UI Components

// 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>
  );
}

Transaction Components

  • TxLink - Transaction link
  • TxGas - Gas fee display
  • PaymentBeneficiaries - Payment beneficiaries list

History Components

  • CustomerInvoiceList - Invoice history list
  • CustomerPaymentList - Payment history list

Context Providers

  • PaymentProvider - Payment context provider
  • DonateProvider - Donation context provider
  • PaymentThemeProvider - Theme provider

Hooks

  • useSubscription - event socket callback
  • useMobile - Mobile detection

Utilities

API Client

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

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

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

// 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

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;
});

Auto-Topup Components

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.

import { AutoTopup, PaymentProvider } from '@blocklet/payment-react';

function CreditManagementPage() {
  return (
    <PaymentProvider session={session}>
      {/* Default mode - fully expanded display */}
      <AutoTopup
        currencyId="credit-currency-id"
        onConfigChange={(config) => {
          console.log('Auto-topup config updated:', config);
        }}
      />

      {/* Simple mode - collapsed by default, expandable */}
      <AutoTopup
        currencyId="credit-currency-id"
        mode="simple"
        onConfigChange={(config) => {
          // Handle configuration changes
          refreshCreditBalance();
        }}
      />

      {/* Custom mode - full control over rendering */}
      <AutoTopup
        currencyId="credit-currency-id"
        mode="custom"
        onConfigChange={(config) => console.log('Config updated:', config)}
      >
        {(openModal, config, paymentData, loading) => (
          <div>
            {loading ? (
              <div>Loading auto-topup configuration...</div>
            ) : (
              <div>
                <h3>Auto Recharge Status</h3>
                <p>Status: {config?.enabled ? 'Active' : 'Inactive'}</p>
                {config?.enabled && (
                  <p>
                    Threshold: {config.threshold} {config.currency?.symbol}
                  </p>
                )}
                {paymentData?.balanceInfo && (
                  <p>
                    Wallet Balance: {paymentData.balanceInfo.token} {config?.rechargeCurrency?.symbol}
                  </p>
                )}
                <button onClick={openModal}>Configure Auto-Topup</button>
              </div>
            )}
          </div>
        )}
      </AutoTopup>
    </PaymentProvider>
  );
}

AutoTopupModal

Configure auto-recharge settings including threshold, payment method, and purchase amount.

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 (
    <PaymentProvider session={session}>
      <button onClick={() => setModalOpen(true)}>
        Setup Auto-Recharge
      </button>

      <AutoTopupModal
        open={modalOpen}
        onClose={() => setModalOpen(false)}
        currencyId={currencyId}
        onSuccess={handleSuccess}
        onError={handleError}
        defaultEnabled={true} // Start with auto-recharge enabled
      />
    </PaymentProvider>
  );
}

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) => ReactNode

AutoTopupModal 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

Donation Page Example

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>
  );
}

Auto-Topup Integration Example

Complete example showing how to integrate auto-topup functionality into a credit management dashboard.

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 (
    <PaymentProvider session={session}>
      <Grid container spacing={3}>
        {/* Credit Balance Cards with Auto-Topup */}
        {creditCurrencies.map((currency) => (
          <Grid item xs={12} md={6} key={currency.id}>
            <Card>
              <CardContent>
                <Typography variant="h6" gutterBottom>
                  {currency.name} Balance
                </Typography>
                
                {/* Current balance display */}
                <Typography variant="h4" color="primary" gutterBottom>
                  <Amount
                    amount={currency.balance}
                    decimal={currency.decimal}
                    symbol={currency.symbol}
                  />
                </Typography>
                
                {/* Auto-topup configuration */}
                <AutoTopup
                  currencyId={currency.id}
                  mode="simple"
                  onConfigChange={(config) => 
                    handleAutoTopupChange(currency.id, config)
                  }
                  sx={{ mt: 2 }}
                />
                
                {/* Manual setup button for currencies without auto-topup */}
                <Button
                  variant="outlined"
                  onClick={() => {
                    setSelectedCurrency(currency);
                    setShowSetupModal(true);
                  }}
                  sx={{ mt: 1 }}
                >
                  Configure Auto-Recharge
                </Button>
              </CardContent>
            </Card>
          </Grid>
        ))}

        {/* Usage History */}
        <Grid item xs={12}>
          <Card>
            <CardContent>
              <Typography variant="h6" gutterBottom>
                Credit Usage History
              </Typography>
              <CustomerInvoiceList
                customer_id={session?.user?.did}
                type="table"
                include_staking
                status="paid,open"
              />
            </CardContent>
          </Card>
        </Grid>
      </Grid>

      {/* Auto-topup Setup Modal */}
      {showSetupModal && selectedCurrency && (
        <AutoTopupModal
          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}
        />
      )}
    </PaymentProvider>
  );
}

// Custom auto-topup display using custom mode
function CustomAutoTopupDisplay({ currencyId }) {
  return (
    <AutoTopup
      currencyId={currencyId}
      mode="custom"
    >
      {(openModal, config, paymentData, loading) => {
        if (loading) return <div>Loading...</div>;

        return (
          <Card variant="outlined">
            <CardContent>
              <Typography variant="subtitle1" gutterBottom>
                Smart Auto-Recharge
              </Typography>
              
              {config?.enabled ? (
                <div>
                  <Typography color="success.main" gutterBottom>
                    ✓ Active - Recharges when balance drops below {config.threshold} {config.currency?.symbol}
                  </Typography>
                  
                  <Typography variant="body2" color="text.secondary">
                    Next recharge: {config.quantity}x {config.price?.product?.name}
                  </Typography>
                  
                  {paymentData?.balanceInfo && (
                    <Typography variant="body2" sx={{ mt: 1 }}>
                      Wallet Balance: {paymentData.balanceInfo.token} {config.rechargeCurrency?.symbol}
                    </Typography>
                  )}
                </div>
              ) : (
                <Typography color="text.secondary" gutterBottom>
                  Auto-recharge is not configured
                </Typography>
              )}
              
              <Button
                variant="contained"
                size="small"
                onClick={openModal}
                sx={{ mt: 2 }}
              >
                {config?.enabled ? 'Modify Settings' : 'Setup Auto-Recharge'}
              </Button>
            </CardContent>
          </Card>
        );
      }}
    </AutoTopup>
  );
}

Subscription Management Example

  • 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
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 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)
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>
  );
}

Best Practices

Cache Management

// 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);
}

Bundle Optimization

  • Use lazy loading for non-critical components
  • Import only required components
  • Leverage code splitting with dynamic imports

Theme Consistency

  • Maintain consistent styling across components
  • Use theme provider for global style changes
  • Override styles at component level when needed

Theme Customization

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)',
        },
      },
    },
  }}
/>

Status & Utility Components

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

Readme

Keywords

Package Sidebar

Install

npm i @blocklet/payment-react

Weekly Downloads

799

Version

1.20.3

License

Apache-2.0

Unpacked Size

1.96 MB

Total Files

455

Last publish

Collaborators

  • wangshijun
  • gxw