@blocklet/payment-react 1.19.23 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aigne/doc-smith/config.yaml +114 -0
- package/.aigne/doc-smith/output/structure-plan.json +361 -0
- package/.aigne/doc-smith/preferences.yml +55 -0
- package/.aigne/doc-smith/upload-cache.yaml +264 -0
- package/README.md +2 -3
- package/docs/_sidebar.md +33 -0
- package/docs/components-business-auto-topup.md +238 -0
- package/docs/components-business-overdue-invoice-payment.md +231 -0
- package/docs/components-business-resume-subscription.md +177 -0
- package/docs/components-business.md +45 -0
- package/docs/components-checkout-checkout-donate.md +199 -0
- package/docs/components-checkout-checkout-form.md +185 -0
- package/docs/components-checkout-checkout-table.md +228 -0
- package/docs/components-checkout.md +131 -0
- package/docs/components-history-credit-grants-list.md +98 -0
- package/docs/components-history-credit-transactions-list.md +116 -0
- package/docs/components-history-invoice-list.md +104 -0
- package/docs/components-history-payment-list.md +65 -0
- package/docs/components-history.md +92 -0
- package/docs/components-ui-form-elements-address-form.md +150 -0
- package/docs/components-ui-form-elements-country-select.md +105 -0
- package/docs/components-ui-form-elements-currency-selector.md +124 -0
- package/docs/components-ui-form-elements-phone-input.md +160 -0
- package/docs/components-ui-form-elements.md +125 -0
- package/docs/components-ui-payment-summary.md +157 -0
- package/docs/components-ui-pricing-table.md +227 -0
- package/docs/components-ui.md +44 -0
- package/docs/components.md +95 -0
- package/docs/getting-started.md +111 -0
- package/docs/guides-theming.md +175 -0
- package/docs/guides-utilities.md +235 -0
- package/docs/guides.md +95 -0
- package/docs/hooks-use-mobile.md +70 -0
- package/docs/hooks-use-subscription.md +129 -0
- package/docs/hooks.md +84 -0
- package/docs/overview.md +87 -0
- package/docs/providers-donate-provider.md +175 -0
- package/docs/providers-payment-provider.md +245 -0
- package/docs/providers.md +101 -0
- package/es/payment/form/index.js +15 -1
- package/lib/payment/form/index.js +14 -1
- package/package.json +5 -5
- package/src/payment/form/index.tsx +16 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Utilities
|
|
2
|
+
|
|
3
|
+
The `@blocklet/payment-react` library exports several utility functions and classes to simplify common tasks in your application, such as client-side caching, date formatting, and internationalization. These tools are designed to work seamlessly with the library's components and the underlying payment infrastructure.
|
|
4
|
+
|
|
5
|
+
## API Client
|
|
6
|
+
|
|
7
|
+
The library exports a pre-configured Axios instance for making API requests to your payment backend. It automatically handles base URLs and authentication when used within the `PaymentProvider` context. For more details on setting up the provider, see the [PaymentProvider documentation](./providers-payment-provider.md).
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { api } from '@blocklet/payment-react';
|
|
11
|
+
|
|
12
|
+
// Basic GET request
|
|
13
|
+
const response = await api.get('/api/payments');
|
|
14
|
+
|
|
15
|
+
// POST request with a body
|
|
16
|
+
const data = await api.post('/api/checkout', { amount: 100 });
|
|
17
|
+
|
|
18
|
+
// Request with query parameters
|
|
19
|
+
const results = await api.get('/api/invoices', {
|
|
20
|
+
params: { status: 'paid' }
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Request with custom configuration
|
|
24
|
+
const config = {
|
|
25
|
+
headers: { 'Custom-Header': 'value' }
|
|
26
|
+
};
|
|
27
|
+
const response = await api.put('/api/subscription', data, config);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## CachedRequest
|
|
31
|
+
|
|
32
|
+
The `CachedRequest` class offers a straightforward way to implement client-side caching, reducing redundant network calls and improving your application's responsiveness. It supports multiple caching strategies and time-to-live (TTL) configurations.
|
|
33
|
+
|
|
34
|
+
```d2
|
|
35
|
+
direction: down
|
|
36
|
+
|
|
37
|
+
fetch: "Call priceRequest.fetch()"
|
|
38
|
+
cache_check: "Is data in cache and not expired?"
|
|
39
|
+
|
|
40
|
+
fetch -> cache_check
|
|
41
|
+
|
|
42
|
+
subgraph "Cache Hit" {
|
|
43
|
+
direction: down
|
|
44
|
+
style.stroke: "#4CAF50"
|
|
45
|
+
return_cached: "Return cached data"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
subgraph "Cache Miss" {
|
|
49
|
+
direction: down
|
|
50
|
+
style.stroke: "#F44336"
|
|
51
|
+
api_call: "Execute api.get('/api/prices')"
|
|
52
|
+
store_cache: "Store response in cache with timestamp"
|
|
53
|
+
return_fresh: "Return fresh data"
|
|
54
|
+
api_call -> store_cache -> return_fresh
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
cache_check -> return_cached: Yes
|
|
58
|
+
cache_check -> api_call: No
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Usage
|
|
62
|
+
|
|
63
|
+
To use it, create an instance of `CachedRequest` with a unique key, a data-fetching function, and optional configuration.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { CachedRequest, api } from '@blocklet/payment-react';
|
|
67
|
+
|
|
68
|
+
// Create a cached request instance
|
|
69
|
+
const priceRequest = new CachedRequest(
|
|
70
|
+
'product-prices',
|
|
71
|
+
() => api.get('/api/prices'),
|
|
72
|
+
{
|
|
73
|
+
strategy: 'session', // 'session' | 'local' | 'memory'
|
|
74
|
+
ttl: 5 * 60 * 1000 // Cache for 5 minutes
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Use the cached request
|
|
79
|
+
async function fetchPrices() {
|
|
80
|
+
// This will use the cache if available and not expired
|
|
81
|
+
const prices = await priceRequest.fetch();
|
|
82
|
+
|
|
83
|
+
// To bypass the cache and force a network request
|
|
84
|
+
const freshPrices = await priceRequest.fetch(true);
|
|
85
|
+
|
|
86
|
+
return prices;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Configuration Options
|
|
91
|
+
|
|
92
|
+
| Option | Type | Description |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `strategy` | `'session'` \| `'local'` \| `'memory'` | The caching strategy to use. `session` uses `sessionStorage`, `local` uses `localStorage`, and `memory` uses a global in-memory cache. Defaults to `'session'`. |
|
|
95
|
+
| `ttl` | `number` | Time-to-live in milliseconds. After this duration, the cached data is considered expired. Defaults to `0` (no expiration). |
|
|
96
|
+
|
|
97
|
+
## Date Handling with dayjs
|
|
98
|
+
|
|
99
|
+
The library exports a pre-configured `dayjs` instance with useful plugins already enabled, including `relativeTime`, `localizedFormat`, `duration`, `utc`, and `timezone`. This ensures consistent date and time handling across your application.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { dayjs } from '@blocklet/payment-react';
|
|
103
|
+
|
|
104
|
+
// Format the current date
|
|
105
|
+
const formattedDate = dayjs().format('YYYY-MM-DD');
|
|
106
|
+
|
|
107
|
+
// Parse a timestamp and get its Unix value
|
|
108
|
+
const timestamp = 1672531200000;
|
|
109
|
+
const dateObject = dayjs(timestamp);
|
|
110
|
+
const unixValue = dateObject.unix();
|
|
111
|
+
|
|
112
|
+
// Display relative time
|
|
113
|
+
const fiveMinutesAgo = dayjs().subtract(5, 'minute');
|
|
114
|
+
const relativeString = dayjs().from(fiveMinutesAgo);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Formatting Utilities
|
|
118
|
+
|
|
119
|
+
A collection of functions to format data for display, from prices and statuses to plain text.
|
|
120
|
+
|
|
121
|
+
### Price and Amount Formatting
|
|
122
|
+
|
|
123
|
+
These functions help display monetary values and quantities correctly according to currency decimals and locale conventions.
|
|
124
|
+
|
|
125
|
+
- `formatPrice(price, currency, unit_label, quantity)`: Formats a full price object into a human-readable string, including recurring intervals (e.g., "$10.00 / month").
|
|
126
|
+
- `formatAmount(amount, decimals)`: Formats a raw amount string based on the currency's decimal places.
|
|
127
|
+
- `formatBNStr(str, decimals, precision)`: Formats a BigNumber string into a readable number, handling large values and token conversions.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { formatPrice, formatAmount } from '@blocklet/payment-react';
|
|
131
|
+
|
|
132
|
+
// Example price and currency objects (simplified)
|
|
133
|
+
const price = { type: 'recurring', recurring: { interval: 'month', interval_count: 1 }, unit_amount: '1000' };
|
|
134
|
+
const currency = { symbol: '$', decimal: 2 };
|
|
135
|
+
|
|
136
|
+
const displayPrice = formatPrice(price, currency);
|
|
137
|
+
// Result: "10.00 $ per month"
|
|
138
|
+
|
|
139
|
+
const displayAmount = formatAmount('12345', 2);
|
|
140
|
+
// Result: "123.45"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Status Colors
|
|
144
|
+
|
|
145
|
+
Several utility functions map resource statuses to semantic colors (`success`, `warning`, `error`, `primary`, `default`) for consistent UI indicators in components like `<Status />`.
|
|
146
|
+
|
|
147
|
+
| Function | Status Examples | Color |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `getSubscriptionStatusColor` | `active`, `trialing` | `success`, `primary` |
|
|
150
|
+
| `getInvoiceStatusColor` | `paid`, `open`, `uncollectible` | `success`, `secondary`, `warning` |
|
|
151
|
+
| `getPaymentIntentStatusColor` | `succeeded`, `requires_action` | `success`, `warning` |
|
|
152
|
+
| `getRefundStatusColor` | `succeeded`, `pending` | `success`, `default` |
|
|
153
|
+
|
|
154
|
+
### Text Utilities
|
|
155
|
+
|
|
156
|
+
- `truncateText(text, maxLength, useWidth)`: Truncates a string to a specified length, adding an ellipsis. The `useWidth` option calculates length based on character width for better CJK character handling.
|
|
157
|
+
|
|
158
|
+
## Internationalization (i18n)
|
|
159
|
+
|
|
160
|
+
For applications targeting a global audience, the library includes utilities to manage translations.
|
|
161
|
+
|
|
162
|
+
### `createTranslator`
|
|
163
|
+
|
|
164
|
+
You can create your own translator instance with custom language packs.
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { createTranslator } from '@blocklet/payment-react';
|
|
168
|
+
|
|
169
|
+
const myTranslations = {
|
|
170
|
+
en: {
|
|
171
|
+
checkout: { title: 'Complete Payment' }
|
|
172
|
+
},
|
|
173
|
+
zh: {
|
|
174
|
+
checkout: { title: '完成支付' }
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const translator = createTranslator({ fallbackLocale: 'en' }, myTranslations);
|
|
179
|
+
|
|
180
|
+
console.log(translator('checkout.title', 'zh')); // Outputs: '完成支付'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Merging with Library Translations
|
|
184
|
+
|
|
185
|
+
To leverage the built-in translations for payment components, you can merge them with your application's translation files.
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { translations as paymentTranslations } from '@blocklet/payment-react';
|
|
189
|
+
import merge from 'lodash/merge';
|
|
190
|
+
|
|
191
|
+
import en from './en'; // Your app's English translations
|
|
192
|
+
import zh from './zh'; // Your app's Chinese translations
|
|
193
|
+
|
|
194
|
+
export const translations = merge(
|
|
195
|
+
{
|
|
196
|
+
zh,
|
|
197
|
+
en,
|
|
198
|
+
},
|
|
199
|
+
paymentTranslations
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Lazy Loading Components
|
|
204
|
+
|
|
205
|
+
To optimize your application's bundle size, you can use the `createLazyComponent` utility to dynamically load components only when they are needed.
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { createLazyComponent } from '@blocklet/payment-react';
|
|
209
|
+
|
|
210
|
+
const LazyLoadedComponent = createLazyComponent(async () => {
|
|
211
|
+
// Dynamically import the component and its dependencies
|
|
212
|
+
const [{ Component }, { useHook }] = await Promise.all([
|
|
213
|
+
import('./Component'),
|
|
214
|
+
import('./hooks')
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
// Make dependencies available if needed
|
|
218
|
+
globalThis.__DEPENDENCIES__ = { useHook };
|
|
219
|
+
|
|
220
|
+
return Component;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
function App() {
|
|
224
|
+
return (
|
|
225
|
+
<div>
|
|
226
|
+
{/* This component will be loaded on demand */}
|
|
227
|
+
<LazyLoadedComponent />
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
These utilities provide robust solutions for common development challenges. To learn about the custom React hooks for handling side effects and state, proceed to the [Hooks](./hooks.md) guide.
|
package/docs/guides.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Guides
|
|
2
|
+
|
|
3
|
+
Welcome to the guides section. While the Components documentation covers individual UI pieces, these guides focus on broader topics that span the entire library. Here, you'll learn about advanced subjects like customizing the appearance of your payment flows, using powerful utility functions, and implementing patterns to create a robust and polished user experience.
|
|
4
|
+
|
|
5
|
+
These guides are designed for developers looking to move beyond basic integration and fully leverage the capabilities of `@blocklet/payment-react`.
|
|
6
|
+
|
|
7
|
+
<x-cards>
|
|
8
|
+
<x-card data-title="Theming" data-icon="lucide:palette" data-href="/guides/theming">
|
|
9
|
+
Learn how to customize the look and feel of payment components to match your application's branding using Material-UI themes.
|
|
10
|
+
</x-card>
|
|
11
|
+
<x-card data-title="Utilities" data-icon="lucide:wrench" data-href="/guides/utilities">
|
|
12
|
+
Explore a suite of helper functions for common tasks like cached data fetching, date formatting, and internationalization.
|
|
13
|
+
</x-card>
|
|
14
|
+
</x-cards>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Customizing Appearance
|
|
19
|
+
|
|
20
|
+
The library is built on Material-UI and exposes a flexible theming system. You can provide a custom theme configuration to adjust everything from colors and typography to the shape of buttons and inputs. For a comprehensive overview, see the full [Theming Guide](./guides-theming.md).
|
|
21
|
+
|
|
22
|
+
For example, you can change the primary button color by passing a `theme` object to a component. Note that components requiring session context must be wrapped in `PaymentProvider`.
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { PaymentProvider, CheckoutForm } from '@blocklet/payment-react';
|
|
26
|
+
// useSessionContext is a hook from your app's session management setup.
|
|
27
|
+
import { useSessionContext } from '../hooks/session';
|
|
28
|
+
|
|
29
|
+
function MyCheckoutPage() {
|
|
30
|
+
const { session, connectApi } = useSessionContext();
|
|
31
|
+
|
|
32
|
+
// For a complete guide on setting up PaymentProvider and session context,
|
|
33
|
+
// please refer to the PaymentProvider documentation.
|
|
34
|
+
if (!session) {
|
|
35
|
+
return <div>Loading session...</div>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<PaymentProvider session={session} connect={connectApi}>
|
|
40
|
+
<CheckoutForm
|
|
41
|
+
id="plink_xxx"
|
|
42
|
+
theme={{
|
|
43
|
+
components: {
|
|
44
|
+
MuiButton: {
|
|
45
|
+
styleOverrides: {
|
|
46
|
+
containedPrimary: {
|
|
47
|
+
backgroundColor: '#1DC1C7',
|
|
48
|
+
color: '#fff',
|
|
49
|
+
'&:hover': {
|
|
50
|
+
backgroundColor: 'rgb(20, 135, 139)',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
</PaymentProvider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Simplifying Logic with Utilities
|
|
64
|
+
|
|
65
|
+
Beyond components, `@blocklet/payment-react` exports a suite of utility functions and classes to handle common tasks. The `CachedRequest` class, for instance, provides an easy way to cache API responses, reducing network load and improving performance. Discover more tools in our [Utilities Guide](./guides-utilities.md).
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { CachedRequest, api } from '@blocklet/payment-react';
|
|
69
|
+
|
|
70
|
+
// Create a cached request that fetches prices and caches them for 5 minutes
|
|
71
|
+
const priceRequest = new CachedRequest(
|
|
72
|
+
'product-prices',
|
|
73
|
+
() => api.get('/api/prices'),
|
|
74
|
+
{
|
|
75
|
+
strategy: 'session', // 'session' | 'local' | 'memory'
|
|
76
|
+
ttl: 5 * 60 * 1000 // 5 minutes
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
async function fetchPrices() {
|
|
81
|
+
// Will use cache if available and not expired
|
|
82
|
+
const prices = await priceRequest.fetch();
|
|
83
|
+
|
|
84
|
+
// Force a refresh from the network
|
|
85
|
+
const freshPrices = await priceRequest.fetch(true);
|
|
86
|
+
|
|
87
|
+
return prices;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Next Steps
|
|
94
|
+
|
|
95
|
+
After mastering these advanced topics, proceed to the [Hooks](./hooks.md) documentation to learn about handling real-time events and responsive design.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# useMobile
|
|
2
|
+
|
|
3
|
+
The `useMobile` hook is a convenient utility for creating responsive layouts in your application. It helps determine if the current viewport width is considered 'mobile' based on standard Material-UI breakpoints.
|
|
4
|
+
|
|
5
|
+
This hook simplifies responsive logic by abstracting away the underlying `useTheme` and `useMediaQuery` calls from Material-UI.
|
|
6
|
+
|
|
7
|
+
## Basic Usage
|
|
8
|
+
|
|
9
|
+
Import the hook and use it within your component to get a boolean value indicating if the screen is mobile-sized, along with the corresponding pixel width for that breakpoint.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { useMobile } from '@blocklet/payment-react';
|
|
13
|
+
import Typography from '@mui/material/Typography';
|
|
14
|
+
|
|
15
|
+
function ResponsiveComponent() {
|
|
16
|
+
const { isMobile } = useMobile();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Typography variant="h5">
|
|
20
|
+
{isMobile ? 'This is the mobile view.' : 'This is the desktop view.'}
|
|
21
|
+
</Typography>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
In this example, the component will render different text depending on whether the screen width is below the default 'md' (medium) breakpoint.
|
|
27
|
+
|
|
28
|
+
## Parameters
|
|
29
|
+
|
|
30
|
+
The `useMobile` hook accepts an optional parameter to customize the breakpoint used for mobile detection.
|
|
31
|
+
|
|
32
|
+
| Name | Type | Default | Description |
|
|
33
|
+
|---------------|--------------------------------------------|---------|---------------------------------------------------------------------------------------------------------------|
|
|
34
|
+
| `mobilePoint` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` \| `'xl'` | `'md'` | The Material-UI breakpoint to use as the threshold. The hook will return `true` for screen sizes at or below this point. |
|
|
35
|
+
|
|
36
|
+
## Return Value
|
|
37
|
+
|
|
38
|
+
The hook returns an object with two properties:
|
|
39
|
+
|
|
40
|
+
| Key | Type | Description |
|
|
41
|
+
|--------------|-----------|---------------------------------------------------------------------------------------------------------|
|
|
42
|
+
| `isMobile` | `boolean` | `true` if the screen width is at or below the specified `mobilePoint`, otherwise `false`. |
|
|
43
|
+
| `mobileSize` | `string` | A string representing the pixel width of the `mobilePoint` breakpoint (e.g., `'900px'` for the `'md'` breakpoint). |
|
|
44
|
+
|
|
45
|
+
## Example with Custom Breakpoint
|
|
46
|
+
|
|
47
|
+
You can specify a different breakpoint if your application's design requires a different threshold for mobile view.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { useMobile } from '@blocklet/payment-react';
|
|
51
|
+
import Box from '@mui/material/Box';
|
|
52
|
+
|
|
53
|
+
function CustomBreakpointComponent() {
|
|
54
|
+
// Consider 'sm' and below as mobile
|
|
55
|
+
const { isMobile, mobileSize } = useMobile('sm');
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Box sx={{ p: 2, border: '1px solid grey' }}>
|
|
59
|
+
<p>The mobile breakpoint is set to {mobileSize}.</p>
|
|
60
|
+
{isMobile ? (
|
|
61
|
+
<p>This layout is optimized for smaller screens.</p>
|
|
62
|
+
) : (
|
|
63
|
+
<p>This layout is for tablets and desktops.</p>
|
|
64
|
+
)}
|
|
65
|
+
</Box>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This component adjusts its responsive behavior based on the 'sm' (small) breakpoint, providing more granular control over your UI.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# useSubscription
|
|
2
|
+
|
|
3
|
+
The `useSubscription` hook provides a straightforward way to subscribe to real-time events from the payment service. It manages the WebSocket connection lifecycle, allowing your components to react instantly to backend events like `invoice.paid` or `subscription.updated`.
|
|
4
|
+
|
|
5
|
+
This is essential for creating dynamic user experiences where the UI needs to reflect changes without requiring manual refreshes.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
The hook abstracts the complexity of managing a WebSocket connection. When your component mounts, `useSubscription` establishes a connection to the relay service, subscribes to the specified channel, and returns a subscription object. This object can then be used to listen for incoming events. The hook also handles cleanup, disconnecting and unsubscribing when the component unmounts.
|
|
10
|
+
|
|
11
|
+
```d2
|
|
12
|
+
shape: sequence_diagram
|
|
13
|
+
|
|
14
|
+
Component: "Your React Component"
|
|
15
|
+
Hook: "useSubscription Hook"
|
|
16
|
+
WsClient: "WebSocket Client"
|
|
17
|
+
Relay: "Relay Service"
|
|
18
|
+
|
|
19
|
+
Component -> Hook: "Renders and calls useSubscription(\"invoice_xyz\")"
|
|
20
|
+
Hook -> WsClient: "Establishes WebSocket connection if not connected"
|
|
21
|
+
WsClient -> Relay: "Connects to service endpoint"
|
|
22
|
+
Hook -> WsClient: "Subscribes to formatted channel (e.g., relay:appId:invoice_xyz)"
|
|
23
|
+
WsClient -> Relay: "Sends subscription request"
|
|
24
|
+
Relay -> WsClient: "Confirms subscription"
|
|
25
|
+
Hook -> Component: "Returns subscription object"
|
|
26
|
+
|
|
27
|
+
Component -> Component: "Attaches event listener (e.g., subscription.on('update', ...))"
|
|
28
|
+
|
|
29
|
+
Relay -> WsClient: "Pushes 'update' event with data"
|
|
30
|
+
WsClient -> Hook: "Forwards event to the subscription"
|
|
31
|
+
Hook -> Component: "Triggers the attached event listener"
|
|
32
|
+
Component -> Component: "Updates state (e.g., setStatus('Paid'))"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
To use the hook, simply pass the channel you wish to subscribe to. The underlying implementation requires that the channel name does not contain special characters like `/`, `.`, or `:`, as this will prevent the client from receiving events.
|
|
38
|
+
|
|
39
|
+
### Parameters
|
|
40
|
+
|
|
41
|
+
| Name | Type | Description |
|
|
42
|
+
| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
43
|
+
| `channel` | `string` | **Required**. A unique identifier for the subscription channel. It must not contain special characters like `/`, `.`, or `:`. The hook prefixes this with the application ID to create the final channel name (e.g., `relay:appId:channel`). |
|
|
44
|
+
|
|
45
|
+
### Return Value
|
|
46
|
+
|
|
47
|
+
The hook returns a `subscription` object, which is an instance of the WebSocket client's subscription handler. You can attach event listeners to this object using its `.on(eventName, callback)` method.
|
|
48
|
+
|
|
49
|
+
## Example
|
|
50
|
+
|
|
51
|
+
Here is an example of a component that displays the status of an invoice. It uses `useSubscription` to listen for real-time updates and changes the status from "Pending" to "Paid" when an `invoice.paid` event is received.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import React, { useState, useEffect } from 'react';
|
|
55
|
+
import { PaymentProvider, useSubscription } from '@blocklet/payment-react';
|
|
56
|
+
|
|
57
|
+
// Assume this hook is defined in your application to provide session context.
|
|
58
|
+
// See the PaymentProvider documentation for an example implementation.
|
|
59
|
+
import { useSessionContext } from './session-context';
|
|
60
|
+
|
|
61
|
+
interface InvoiceStatusProps {
|
|
62
|
+
invoiceId: string;
|
|
63
|
+
initialStatus: 'pending' | 'paid' | 'failed';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function InvoiceStatus({ invoiceId, initialStatus }: InvoiceStatusProps) {
|
|
67
|
+
const [status, setStatus] = useState(initialStatus);
|
|
68
|
+
|
|
69
|
+
// The channel should be a simple string.
|
|
70
|
+
const subscription = useSubscription(invoiceId);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
// Ensure the subscription object is available before attaching listeners.
|
|
74
|
+
if (subscription) {
|
|
75
|
+
const handleInvoiceUpdate = (event: { status: string }) => {
|
|
76
|
+
console.log('Received event:', event);
|
|
77
|
+
if (event.status === 'paid') {
|
|
78
|
+
setStatus('paid');
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Listen for a specific event name.
|
|
83
|
+
// The actual event name depends on your backend implementation.
|
|
84
|
+
subscription.on('invoice.paid', handleInvoiceUpdate);
|
|
85
|
+
|
|
86
|
+
// Cleanup function to remove the listener when the component
|
|
87
|
+
// unmounts or the subscription object changes.
|
|
88
|
+
return () => {
|
|
89
|
+
subscription.off('invoice.paid', handleInvoiceUpdate);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}, [subscription]); // Re-run effect if the subscription object changes.
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div>
|
|
96
|
+
<h2>Invoice Status</h2>
|
|
97
|
+
<p>Invoice ID: {invoiceId}</p>
|
|
98
|
+
<p>Status: <strong>{status.toUpperCase()}</strong></p>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Example of how to use the InvoiceStatus component within an application.
|
|
104
|
+
function App() {
|
|
105
|
+
const { session, connectApi } = useSessionContext();
|
|
106
|
+
|
|
107
|
+
// Render the component only after the session is loaded.
|
|
108
|
+
if (!session) {
|
|
109
|
+
return <div>Loading session...</div>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<PaymentProvider session={session} connect={connectApi}>
|
|
114
|
+
<InvoiceStatus invoiceId="invoice_12345" initialStatus="pending" />
|
|
115
|
+
</PaymentProvider>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default App;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
In this example:
|
|
123
|
+
1. The `App` component calls `useSessionContext()` to retrieve the user `session` and `connectApi` client.
|
|
124
|
+
2. It renders the `PaymentProvider`, which provides the necessary context to its children. For details on setting up your session context, please refer to the [PaymentProvider](./providers-payment-provider.md) documentation.
|
|
125
|
+
3. The `InvoiceStatus` component subscribes to a channel named after the `invoiceId`.
|
|
126
|
+
4. An effect is set up to listen for changes to the `subscription` object.
|
|
127
|
+
5. Once the `subscription` object is available, an event handler `handleInvoiceUpdate` is attached to the `invoice.paid` event.
|
|
128
|
+
6. When a matching event is received from the server, the handler updates the component's state, and the UI re-renders to show the "PAID" status.
|
|
129
|
+
7. The `useEffect` cleanup function ensures the event listener is removed to prevent memory leaks.
|
package/docs/hooks.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Hooks
|
|
2
|
+
|
|
3
|
+
The `@blocklet/payment-react` library includes custom React hooks to manage specific functionalities and side effects within your application. These hooks abstract away complex logic, making it easier to handle real-time events and create responsive user interfaces.
|
|
4
|
+
|
|
5
|
+
This section provides an overview of the available hooks. For detailed usage and examples, refer to the specific hook's documentation.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## useSubscription
|
|
10
|
+
|
|
11
|
+
The `useSubscription` hook establishes a WebSocket connection to listen for real-time events from the payment service. It is essential for scenarios where you need to react to asynchronous updates, such as a payment being successfully processed or an invoice status changing.
|
|
12
|
+
|
|
13
|
+
**When to Use:**
|
|
14
|
+
- When you need to update the UI in real-time based on backend payment events.
|
|
15
|
+
- To listen for `invoice.paid`, `subscription.updated`, or other critical events without constant polling.
|
|
16
|
+
|
|
17
|
+
**Basic Usage:**
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { useSubscription } from '@blocklet/payment-react';
|
|
21
|
+
import { useEffect } from 'react';
|
|
22
|
+
|
|
23
|
+
function RealTimeInvoiceStatus({ invoiceId }) {
|
|
24
|
+
// The channel's value cannot contain separators like /, ., :
|
|
25
|
+
const channel = `invoice_${invoiceId}`;
|
|
26
|
+
const subscription = useSubscription(channel);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (subscription) {
|
|
30
|
+
subscription.on('invoice.paid', (data) => {
|
|
31
|
+
console.log('Invoice paid!', data);
|
|
32
|
+
// Update UI to show paid status
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}, [subscription]);
|
|
36
|
+
|
|
37
|
+
return <div>Listening for updates on invoice {invoiceId}...</div>;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
➡️ **[Learn more about `useSubscription`](./hooks-use-subscription.md)**
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## useMobile
|
|
46
|
+
|
|
47
|
+
The `useMobile` hook is a simple utility for detecting the viewport size. It helps determine if the application is being viewed on a mobile device based on Material-UI's breakpoint system, which is useful for creating responsive payment flows.
|
|
48
|
+
|
|
49
|
+
**When to Use:**
|
|
50
|
+
- To conditionally render different layouts for mobile and desktop views.
|
|
51
|
+
- To adjust component props (e.g., `mode='inline'` vs `mode='popup'`) based on screen size.
|
|
52
|
+
|
|
53
|
+
**Basic Usage:**
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { PaymentProvider, CheckoutForm, useMobile } from '@blocklet/payment-react';
|
|
57
|
+
// Import your own session context hook
|
|
58
|
+
import { useSessionContext } from '../hooks/session';
|
|
59
|
+
|
|
60
|
+
function ResponsiveCheckout() {
|
|
61
|
+
const { session, connectApi } = useSessionContext();
|
|
62
|
+
const { isMobile } = useMobile();
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<PaymentProvider session={session} connect={connectApi}>
|
|
66
|
+
<CheckoutForm
|
|
67
|
+
id="plink_xxx"
|
|
68
|
+
// Use a different mode on mobile devices
|
|
69
|
+
mode={isMobile ? 'popup' : 'inline'}
|
|
70
|
+
/>
|
|
71
|
+
</PaymentProvider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
➡️ **[Learn more about `useMobile`](./hooks-use-mobile.md)**
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Next Steps
|
|
81
|
+
|
|
82
|
+
Now that you have an overview of the available hooks, you can explore the rich set of UI and business logic components to build your payment flows.
|
|
83
|
+
|
|
84
|
+
➡️ **[Explore Components](./components.md)**
|