@blocklet/payment-react 1.20.5 → 1.20.6
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 +2 -2
- package/.aigne/doc-smith/upload-cache.yaml +381 -0
- package/docs/components-business-auto-topup.md +128 -179
- package/docs/components-business-overdue-invoice-payment.md +108 -143
- package/docs/components-business-resume-subscription.md +117 -104
- package/docs/components-business.md +12 -36
- package/docs/components-checkout-checkout-donate.md +209 -149
- package/docs/components-checkout-checkout-form.md +115 -136
- package/docs/components-checkout-checkout-table.md +92 -172
- package/docs/components-checkout.md +43 -109
- package/docs/components-history-credit-grants-list.md +45 -70
- package/docs/components-history-credit-transactions-list.md +57 -67
- package/docs/components-history-invoice-list.md +58 -52
- package/docs/components-history-payment-list.md +19 -40
- package/docs/components-history.md +42 -67
- package/docs/components-ui-form-elements-address-form.md +37 -65
- package/docs/components-ui-form-elements-country-select.md +80 -59
- package/docs/components-ui-form-elements-currency-selector.md +57 -73
- package/docs/components-ui-form-elements-phone-input.md +90 -112
- package/docs/components-ui-form-elements.md +46 -80
- package/docs/components-ui-payment-summary.md +71 -119
- package/docs/components-ui-pricing-table.md +117 -204
- package/docs/components-ui.md +59 -32
- package/docs/components.md +89 -62
- package/docs/getting-started.md +36 -63
- package/docs/guides-theming.md +122 -84
- package/docs/guides-utilities.md +107 -145
- package/docs/guides.md +7 -84
- package/docs/hooks-use-mobile.md +50 -36
- package/docs/hooks-use-subscription.md +72 -89
- package/docs/hooks.md +12 -82
- package/docs/overview.md +45 -52
- package/docs/providers-donate-provider.md +73 -95
- package/docs/providers-payment-provider.md +115 -169
- package/docs/providers.md +27 -86
- package/es/locales/en.js +7 -0
- package/es/locales/zh.js +8 -1
- package/es/payment/index.js +3 -0
- package/es/payment/progress-item.d.ts +12 -0
- package/es/payment/progress-item.js +78 -0
- package/es/payment/success.d.ts +4 -1
- package/es/payment/success.js +55 -3
- package/lib/locales/en.js +7 -0
- package/lib/locales/zh.js +8 -1
- package/lib/payment/index.js +3 -0
- package/lib/payment/progress-item.d.ts +12 -0
- package/lib/payment/progress-item.js +107 -0
- package/lib/payment/success.d.ts +4 -1
- package/lib/payment/success.js +59 -3
- package/package.json +3 -3
- package/src/locales/en.tsx +7 -0
- package/src/locales/zh.tsx +8 -1
- package/src/payment/index.tsx +6 -0
- package/src/payment/progress-item.tsx +107 -0
- package/src/payment/success.tsx +88 -3
|
@@ -1,125 +1,91 @@
|
|
|
1
1
|
# Form Elements
|
|
2
2
|
|
|
3
|
-
The `@blocklet/payment-react` library provides a collection of granular input components
|
|
3
|
+
The `@blocklet/payment-react` library provides a collection of granular input components designed to simplify the creation of custom payment and contact information forms. These elements are built with validation and user experience in mind, integrating smoothly with `react-hook-form` to provide a robust and flexible solution for data collection.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Each component is designed to be a self-contained unit, allowing you to compose them into complex forms tailored to your specific requirements. Below is an overview of the available form elements.
|
|
6
6
|
|
|
7
7
|
<x-cards data-columns="2">
|
|
8
8
|
<x-card data-title="AddressForm" data-icon="lucide:map-pin" data-href="/components/ui/form-elements/address-form">
|
|
9
|
-
A pre-built form for collecting billing address details
|
|
9
|
+
A pre-built form for collecting billing address details with country-specific validation.
|
|
10
10
|
</x-card>
|
|
11
11
|
<x-card data-title="PhoneInput" data-icon="lucide:phone" data-href="/components/ui/form-elements/phone-input">
|
|
12
|
-
An international phone number input with an integrated country
|
|
12
|
+
An international phone number input with an integrated country code selector and validation.
|
|
13
13
|
</x-card>
|
|
14
14
|
<x-card data-title="CountrySelect" data-icon="lucide:globe" data-href="/components/ui/form-elements/country-select">
|
|
15
|
-
A searchable dropdown for selecting a country, complete with flags and a
|
|
15
|
+
A searchable dropdown for selecting a country, complete with flags and a mobile-friendly UI.
|
|
16
16
|
</x-card>
|
|
17
|
-
<x-card data-title="CurrencySelector" data-icon="lucide:
|
|
18
|
-
A component that allows users to
|
|
17
|
+
<x-card data-title="CurrencySelector" data-icon="lucide:coins" data-href="/components/ui/form-elements/currency-selector">
|
|
18
|
+
A component that allows users to choose their preferred payment currency from available options.
|
|
19
19
|
</x-card>
|
|
20
20
|
</x-cards>
|
|
21
21
|
|
|
22
|
-
## Usage
|
|
22
|
+
## Common Usage Pattern
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
These form elements are intended to be used within a `FormProvider` from `react-hook-form`. You can compose them to build comprehensive forms that capture all the necessary information for a transaction. While each component has its own set of properties, they share a common integration pattern.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Here is a basic example of how you might combine several form elements to create a user information form:
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Here is an example of how to combine them into a single form:
|
|
31
|
-
|
|
32
|
-
```jsx
|
|
28
|
+
```jsx MyCustomForm.tsx icon=logos:react
|
|
33
29
|
import { FormProvider, useForm } from 'react-hook-form';
|
|
34
|
-
import { AddressForm, PhoneInput } from '@blocklet/payment-react';
|
|
35
|
-
import { Button } from '@mui/material';
|
|
36
|
-
|
|
30
|
+
import { AddressForm, PhoneInput, CurrencySelector } from '@blocklet/payment-react';
|
|
31
|
+
import { Button, Stack } from '@mui/material';
|
|
32
|
+
|
|
33
|
+
// Assume `availableCurrencies` is fetched from your backend
|
|
34
|
+
const availableCurrencies = [
|
|
35
|
+
{
|
|
36
|
+
id: 'usd_xxx',
|
|
37
|
+
name: 'USD',
|
|
38
|
+
symbol: 'USD',
|
|
39
|
+
logo: 'url/to/usd_logo.png',
|
|
40
|
+
method: { id: 'stripe', name: 'Stripe' },
|
|
41
|
+
},
|
|
42
|
+
// ... other currencies
|
|
43
|
+
];
|
|
37
44
|
|
|
38
|
-
function
|
|
45
|
+
function MyCustomForm() {
|
|
39
46
|
const methods = useForm({
|
|
40
47
|
defaultValues: {
|
|
41
|
-
|
|
48
|
+
currency: 'usd_xxx',
|
|
49
|
+
phone_number: '',
|
|
42
50
|
billing_address: {
|
|
43
51
|
line1: '',
|
|
44
52
|
city: '',
|
|
45
53
|
state: '',
|
|
46
54
|
postal_code: '',
|
|
47
|
-
country: '
|
|
55
|
+
country: 'US', // Use ISO2 code
|
|
48
56
|
},
|
|
49
57
|
},
|
|
50
58
|
});
|
|
51
59
|
|
|
52
|
-
const validatePhoneNumber = async (phone) => {
|
|
53
|
-
const phoneUtil = await getPhoneUtil();
|
|
54
|
-
return phoneUtil.isValid(phone) || 'Invalid phone number';
|
|
55
|
-
};
|
|
56
|
-
|
|
57
60
|
const onSubmit = (data) => {
|
|
58
|
-
console.log('Form data:', data);
|
|
61
|
+
console.log('Form data submitted:', data);
|
|
59
62
|
};
|
|
60
63
|
|
|
61
64
|
return (
|
|
62
65
|
<FormProvider {...methods}>
|
|
63
66
|
<form onSubmit={methods.handleSubmit(onSubmit)}>
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
<Stack spacing={2}>
|
|
68
|
+
<CurrencySelector
|
|
69
|
+
value={methods.watch('currency')}
|
|
70
|
+
currencies={availableCurrencies}
|
|
71
|
+
onChange={(currencyId) => methods.setValue('currency', currencyId)}
|
|
72
|
+
/>
|
|
73
|
+
<PhoneInput name="phone_number" label="Phone Number" required />
|
|
74
|
+
<AddressForm mode="required" stripe={false} />
|
|
75
|
+
<Button type="submit" variant="contained">
|
|
76
|
+
Submit
|
|
77
|
+
</Button>
|
|
78
|
+
</Stack>
|
|
73
79
|
</form>
|
|
74
80
|
</FormProvider>
|
|
75
81
|
);
|
|
76
82
|
}
|
|
77
83
|
```
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Components like `CurrencySelector` are self-contained and manage their state via props and callbacks, so they don't need to be inside a `FormProvider`.
|
|
82
|
-
|
|
83
|
-
```jsx
|
|
84
|
-
import { CurrencySelector } from '@blocklet/payment-react';
|
|
85
|
-
import { useState } from 'react';
|
|
86
|
-
|
|
87
|
-
// Example currency data structure
|
|
88
|
-
const sampleCurrencies = [
|
|
89
|
-
{
|
|
90
|
-
id: 'usd_credit_card',
|
|
91
|
-
name: 'USD',
|
|
92
|
-
symbol: '$',
|
|
93
|
-
logo: 'https://example.com/usd-logo.png',
|
|
94
|
-
method: { id: 'card', name: 'Credit Card' },
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: 'eth_mainnet',
|
|
98
|
-
name: 'Ethereum',
|
|
99
|
-
symbol: 'ETH',
|
|
100
|
-
logo: 'https://example.com/eth-logo.png',
|
|
101
|
-
method: { id: 'crypto', name: 'Ethereum Network' },
|
|
102
|
-
},
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
function MyCurrencyComponent() {
|
|
106
|
-
const [selectedCurrency, setSelectedCurrency] = useState('usd_credit_card');
|
|
107
|
-
|
|
108
|
-
const handleChange = (currencyId, methodId) => {
|
|
109
|
-
setSelectedCurrency(currencyId);
|
|
110
|
-
console.log('Selected payment method:', methodId);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<CurrencySelector
|
|
115
|
-
currencies={sampleCurrencies}
|
|
116
|
-
value={selectedCurrency}
|
|
117
|
-
onChange={handleChange}
|
|
118
|
-
/>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
```
|
|
85
|
+
In this example, `CurrencySelector`, `PhoneInput`, and `AddressForm` are combined within a single form. `CurrencySelector` is used as a controlled component tied to the form's state, while `PhoneInput` and `AddressForm` are uncontrolled components that automatically register their fields and validation rules with the `react-hook-form` context.
|
|
122
86
|
|
|
123
87
|
## Next Steps
|
|
124
88
|
|
|
125
|
-
|
|
89
|
+
To learn more about the specific properties and implementation details of each component, please explore their individual documentation pages.
|
|
90
|
+
|
|
91
|
+
Start with the [AddressForm](./components-ui-form-elements-address-form.md) to see how to collect billing information.
|
|
@@ -1,157 +1,109 @@
|
|
|
1
1
|
# PaymentSummary
|
|
2
2
|
|
|
3
|
-
The `PaymentSummary` component
|
|
3
|
+
The `PaymentSummary` component is a crucial UI element for any checkout process. It provides a detailed breakdown of the order, including all line items, trial period information, staking requirements, and the final total amount. It's designed to be responsive and adapts its layout for mobile devices.
|
|
4
4
|
|
|
5
|
-
This component works in
|
|
5
|
+
This component works in tandem with `ProductItem` and `ProductDonation` to render each item in the cart, and it handles user interactions like applying cross-sells or changing quantities.
|
|
6
6
|
|
|
7
7
|
## Props
|
|
8
8
|
|
|
9
|
-
The `PaymentSummary` component accepts the following props to customize its behavior and
|
|
10
|
-
|
|
11
|
-
| Prop | Type | Description |
|
|
12
|
-
|
|
13
|
-
| `items` | `TLineItemExpanded[]` |
|
|
14
|
-
| `currency` | `TPaymentCurrency` |
|
|
15
|
-
| `trialInDays` | `number` | The number of days for
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `showStaking` | `boolean` | If `true`, staking
|
|
19
|
-
| `onUpsell` | `(from: string, to: string) => void` | Callback function
|
|
20
|
-
| `onDownsell` | `(from: string) => void` | Callback function
|
|
21
|
-
| `onQuantityChange` | `(itemId: string, quantity: number) => void` | Callback function
|
|
22
|
-
| `onChangeAmount` | `(amount:
|
|
23
|
-
| `onApplyCrossSell` | `(crossSellId: string) => void` | Callback function
|
|
24
|
-
| `onCancelCrossSell` | `() => void` | Callback function
|
|
25
|
-
| `checkoutSessionId` | `string` | The ID of the
|
|
26
|
-
| `crossSellBehavior` | `
|
|
27
|
-
| `donationSettings` | `DonationSettings` | Configuration
|
|
28
|
-
| `action` | `string` | A custom title
|
|
29
|
-
| `completed` | `boolean` | If `true`, disables interactive elements like quantity
|
|
9
|
+
The `PaymentSummary` component accepts the following props to customize its behavior and display:
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Required | Description |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| `items` | `TLineItemExpanded[]` | Yes | An array of line items to be displayed in the summary. |
|
|
14
|
+
| `currency` | `TPaymentCurrency` | Yes | The currency object for formatting amounts. |
|
|
15
|
+
| `trialInDays` | `number` | Yes | The number of trial days for recurring subscriptions. |
|
|
16
|
+
| `billingThreshold` | `number` | Yes | The billing threshold amount. Used in staking calculation if applicable. |
|
|
17
|
+
| `trialEnd` | `number` | No | A Unix timestamp for when the trial ends. Overrides `trialInDays`. |
|
|
18
|
+
| `showStaking` | `boolean` | No | If `true`, displays staking details. Defaults to `false`. |
|
|
19
|
+
| `onUpsell` | `(from: string, to: string) => void` | No | Callback function when a user accepts an upsell offer. |
|
|
20
|
+
| `onDownsell` | `(from: string) => void` | No | Callback function when a user reverts an upsell offer. |
|
|
21
|
+
| `onQuantityChange` | `(itemId: string, quantity: number) => void` | No | Callback function when the quantity of an item is changed. |
|
|
22
|
+
| `onChangeAmount` | `(itemId: string, amount: string) => void` | No | Callback for changing the amount of a custom-amount item, like a donation. |
|
|
23
|
+
| `onApplyCrossSell` | `(crossSellId: string) => void` | No | Callback function when a user adds a cross-sell item. |
|
|
24
|
+
| `onCancelCrossSell` | `() => void` | No | Callback function when a user removes a cross-sell item. |
|
|
25
|
+
| `checkoutSessionId` | `string` | No | The ID of the checkout session, used to fetch potential cross-sell items. |
|
|
26
|
+
| `crossSellBehavior` | `string` | No | Defines the behavior of the cross-sell item (e.g., `'required'`). |
|
|
27
|
+
| `donationSettings` | `DonationSettings` | No | Configuration for donation items. |
|
|
28
|
+
| `action` | `string` | No | A custom title for the summary section, replacing "Order Summary". |
|
|
29
|
+
| `completed` | `boolean` | No | If `true`, disables interactive elements like quantity adjustment. Defaults to `false`. |
|
|
30
30
|
|
|
31
31
|
## Usage
|
|
32
32
|
|
|
33
|
-
The `PaymentSummary` component
|
|
33
|
+
The `PaymentSummary` component should be placed within a `PaymentProvider` and is typically used as part of a larger checkout form. You need to provide it with the line items and currency information.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Here is a basic example of how to integrate the `PaymentSummary` component.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Here is a basic example of rendering a payment summary. The component automatically calculates totals and displays item details based on the `items` and `currency` props.
|
|
40
|
-
|
|
41
|
-
```jsx
|
|
42
|
-
// Note: Ensure this component is rendered within a PaymentProvider.
|
|
37
|
+
```tsx PaymentSummary Example icon=lucide:shopping-cart
|
|
43
38
|
import React from 'react';
|
|
44
|
-
import { PaymentSummary } from '@blocklet/payment-react';
|
|
45
|
-
|
|
46
|
-
const items = [
|
|
47
|
-
{
|
|
48
|
-
price_id: 'price_123',
|
|
49
|
-
quantity: 2,
|
|
50
|
-
price: {
|
|
51
|
-
product: { name: 'Standard Plan', images: ['/logo.png'] },
|
|
52
|
-
type: 'recurring',
|
|
53
|
-
recurring: { interval: 'month', interval_count: 1 },
|
|
54
|
-
unit_amount: '10.00',
|
|
55
|
-
},
|
|
56
|
-
// other TLineItemExpanded properties...
|
|
57
|
-
},
|
|
58
|
-
];
|
|
39
|
+
import { PaymentProvider, PaymentSummary } from '@blocklet/payment-react';
|
|
40
|
+
import { useSessionContext } from '../hooks/session-context'; // Your session context hook
|
|
59
41
|
|
|
60
|
-
|
|
61
|
-
|
|
42
|
+
// Mock data for demonstration purposes
|
|
43
|
+
const mockCurrency = {
|
|
44
|
+
id: 'usd_4573516104843264',
|
|
62
45
|
symbol: '$',
|
|
46
|
+
name: 'USD',
|
|
63
47
|
decimal: 2,
|
|
64
|
-
|
|
48
|
+
isDefault: true,
|
|
65
49
|
};
|
|
66
50
|
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<PaymentSummary
|
|
70
|
-
items={items}
|
|
71
|
-
currency={currency}
|
|
72
|
-
trialInDays={14}
|
|
73
|
-
/>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Displaying Staking and Totals
|
|
79
|
-
|
|
80
|
-
When `showStaking` is set to `true`, the component calculates and displays the required staking amount separately from the payment amount. The final total combines both values.
|
|
81
|
-
|
|
82
|
-
```jsx
|
|
83
|
-
// Note: Ensure this component is rendered within a PaymentProvider.
|
|
84
|
-
import React from 'react';
|
|
85
|
-
import { PaymentSummary } from '@blocklet/payment-react';
|
|
86
|
-
|
|
87
|
-
const recurringItems = [
|
|
51
|
+
const mockItems = [
|
|
88
52
|
{
|
|
89
|
-
|
|
53
|
+
id: 'li_1',
|
|
54
|
+
price_id: 'price_1',
|
|
90
55
|
quantity: 1,
|
|
56
|
+
adjustable_quantity: { enabled: true, minimum: 1, maximum: 10 },
|
|
91
57
|
price: {
|
|
92
|
-
|
|
58
|
+
id: 'price_1',
|
|
93
59
|
type: 'recurring',
|
|
94
|
-
recurring: { interval: '
|
|
95
|
-
unit_amount: '
|
|
60
|
+
recurring: { interval: 'month', interval_count: 1, usage_type: 'licensed' },
|
|
61
|
+
unit_amount: '2000',
|
|
62
|
+
product: {
|
|
63
|
+
name: 'Pro Plan',
|
|
64
|
+
images: [],
|
|
65
|
+
},
|
|
96
66
|
},
|
|
97
67
|
},
|
|
98
68
|
];
|
|
99
|
-
const currency = { id: 'usd', symbol: '$', decimal: 2 };
|
|
100
69
|
|
|
101
|
-
function
|
|
70
|
+
export default function MyCheckoutPage() {
|
|
71
|
+
const { session, connectApi } = useSessionContext();
|
|
72
|
+
|
|
73
|
+
const handleQuantityChange = (itemId, newQuantity) => {
|
|
74
|
+
console.log(`Item ${itemId} quantity changed to ${newQuantity}`);
|
|
75
|
+
// Here you would typically update your state and refetch checkout info
|
|
76
|
+
};
|
|
77
|
+
|
|
102
78
|
return (
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
79
|
+
<PaymentProvider session={session} connectApi={connectApi}>
|
|
80
|
+
<PaymentSummary
|
|
81
|
+
items={mockItems}
|
|
82
|
+
currency={mockCurrency}
|
|
83
|
+
trialInDays={14}
|
|
84
|
+
billingThreshold={0}
|
|
85
|
+
showStaking={true}
|
|
86
|
+
onQuantityChange={handleQuantityChange}
|
|
87
|
+
/>
|
|
88
|
+
</PaymentProvider>
|
|
109
89
|
);
|
|
110
90
|
}
|
|
111
91
|
```
|
|
112
92
|
|
|
113
|
-
###
|
|
93
|
+
### Displaying Staking and Totals
|
|
114
94
|
|
|
115
|
-
|
|
95
|
+
If `showStaking` is set to `true`, the component will calculate and display the required staking amount for recurring items. It separates the amount due for immediate payment from the amount required for staking, providing a clear financial summary to the user. The final total combines these amounts.
|
|
116
96
|
|
|
117
|
-
|
|
118
|
-
// Note: Ensure this component is rendered within a PaymentProvider.
|
|
119
|
-
import React from 'react';
|
|
120
|
-
import { PaymentSummary } from '@blocklet/payment-react';
|
|
97
|
+
### Handling Trial Periods
|
|
121
98
|
|
|
122
|
-
|
|
123
|
-
{
|
|
124
|
-
price_id: 'price_123',
|
|
125
|
-
quantity: 1,
|
|
126
|
-
price: { product: { name: 'Main Product' }, unit_amount: '25.00' },
|
|
127
|
-
},
|
|
128
|
-
];
|
|
129
|
-
const currency = { id: 'usd', symbol: '$', decimal: 2 };
|
|
130
|
-
|
|
131
|
-
function CrossSellCheckoutPage() {
|
|
132
|
-
const handleApplyCrossSell = async (crossSellId) => {
|
|
133
|
-
console.log('Applying cross-sell:', crossSellId);
|
|
134
|
-
// Add logic to update the checkout session via API call
|
|
135
|
-
};
|
|
99
|
+
The component clearly communicates trial periods to the user. By passing `trialInDays` or `trialEnd`, a message like "Then $20.00 / month" will be displayed below the total, informing the user about the recurring charges after the trial concludes.
|
|
136
100
|
|
|
137
|
-
|
|
138
|
-
console.log('Canceling cross-sell');
|
|
139
|
-
// Add logic to update the checkout session via API call
|
|
140
|
-
};
|
|
101
|
+
### Interactive Features
|
|
141
102
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
currency={currency}
|
|
146
|
-
checkoutSessionId="cs_12345"
|
|
147
|
-
crossSellBehavior="required"
|
|
148
|
-
onApplyCrossSell={handleApplyCrossSell}
|
|
149
|
-
onCancelCrossSell={handleCancelCrossSell}
|
|
150
|
-
/>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
```
|
|
103
|
+
- **Quantity Adjustment**: If an item has `adjustable_quantity.enabled` set to `true`, the `PaymentSummary` (via its child `ProductItem`) will render controls to increase or decrease the quantity. The `onQuantityChange` callback is triggered upon modification.
|
|
104
|
+
- **Upsell/Downsell**: For products with an upsell configuration, a switch is rendered to allow users to upgrade their plan. The `onUpsell` and `onDownsell` callbacks handle these actions.
|
|
105
|
+
- **Cross-Sell**: If a `checkoutSessionId` is provided, the component can fetch and display suggested cross-sell items. Buttons to add or remove these items trigger the `onApplyCrossSell` and `onCancelCrossSell` callbacks.
|
|
154
106
|
|
|
155
107
|
### Mobile Responsiveness
|
|
156
108
|
|
|
157
|
-
On mobile devices, the
|
|
109
|
+
On mobile devices, the list of products is collapsible by default to save space, especially for orders with many items. Users can tap to expand or collapse the list, providing a cleaner and more user-friendly experience on smaller screens.
|