@stacksjs/components 0.2.93 → 0.2.94
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/dist/index.js +4 -4
- package/package.json +5 -6
- package/src/components/CodeBlock.stx +0 -54
- package/src/components/Footer.stx +0 -66
- package/src/components/Hero.stx +0 -25
- package/src/components/Installation.stx +0 -37
- package/src/components/index.ts +0 -4
- package/src/composables/index.ts +0 -4
- package/src/composables/useCopyCode.ts +0 -73
- package/src/composables/useDarkMode.ts +0 -100
- package/src/composables/useSEO.ts +0 -230
- package/src/composables/utilities/index.ts +0 -7
- package/src/composables/utilities/useClickOutside.ts +0 -72
- package/src/composables/utilities/useFocusTrap.ts +0 -159
- package/src/composables/utilities/useKeyboard.ts +0 -124
- package/src/composables/utilities/useLocalStorage.ts +0 -132
- package/src/composables/utilities/useMediaQuery.ts +0 -118
- package/src/index.ts +0 -99
- package/src/stx.d.ts +0 -4
- package/src/ui/accordion/Accordion.stx +0 -146
- package/src/ui/accordion/AccordionItem.stx +0 -37
- package/src/ui/accordion/index.ts +0 -47
- package/src/ui/audio/Audio.stx +0 -50
- package/src/ui/audio/README.md +0 -41
- package/src/ui/audio/index.ts +0 -13
- package/src/ui/auth/Login.stx +0 -161
- package/src/ui/auth/README.md +0 -142
- package/src/ui/auth/Signup.stx +0 -159
- package/src/ui/auth/TwoFactorChallenge.stx +0 -111
- package/src/ui/auth/index.ts +0 -32
- package/src/ui/avatar/Avatar.stx +0 -77
- package/src/ui/avatar/index.ts +0 -12
- package/src/ui/badge/Badge.stx +0 -62
- package/src/ui/badge/index.ts +0 -10
- package/src/ui/breadcrumb/Breadcrumb.stx +0 -79
- package/src/ui/breadcrumb/index.ts +0 -15
- package/src/ui/button/Button.stx +0 -74
- package/src/ui/button/README.md +0 -80
- package/src/ui/button/index.ts +0 -85
- package/src/ui/calendar/Calendar.stx +0 -189
- package/src/ui/calendar/README.md +0 -254
- package/src/ui/calendar/index.ts +0 -14
- package/src/ui/card/Card.stx +0 -68
- package/src/ui/card/index.ts +0 -13
- package/src/ui/checkbox/Checkbox.stx +0 -86
- package/src/ui/checkbox/index.ts +0 -16
- package/src/ui/combobox/Combobox.stx +0 -96
- package/src/ui/combobox/ComboboxButton.stx +0 -25
- package/src/ui/combobox/ComboboxInput.stx +0 -32
- package/src/ui/combobox/ComboboxOption.stx +0 -49
- package/src/ui/combobox/ComboboxOptions.stx +0 -16
- package/src/ui/combobox/README.md +0 -150
- package/src/ui/combobox/index.ts +0 -40
- package/src/ui/command-palette/CommandPalette.stx +0 -81
- package/src/ui/command-palette/CommandPaletteItem.stx +0 -27
- package/src/ui/command-palette/README.md +0 -42
- package/src/ui/command-palette/index.ts +0 -15
- package/src/ui/dialog/Dialog.stx +0 -41
- package/src/ui/dialog/DialogBackdrop.stx +0 -9
- package/src/ui/dialog/DialogDescription.stx +0 -11
- package/src/ui/dialog/DialogPanel.stx +0 -16
- package/src/ui/dialog/DialogTitle.stx +0 -11
- package/src/ui/dialog/README.md +0 -121
- package/src/ui/dialog/index.ts +0 -32
- package/src/ui/drawer/Drawer.stx +0 -120
- package/src/ui/drawer/README.md +0 -90
- package/src/ui/drawer/index.ts +0 -9
- package/src/ui/dropdown/Dropdown.stx +0 -82
- package/src/ui/dropdown/DropdownButton.stx +0 -29
- package/src/ui/dropdown/DropdownItem.stx +0 -35
- package/src/ui/dropdown/DropdownItems.stx +0 -18
- package/src/ui/dropdown/README.md +0 -114
- package/src/ui/dropdown/index.ts +0 -29
- package/src/ui/form/Form.stx +0 -95
- package/src/ui/form/index.ts +0 -67
- package/src/ui/heatmap/Heatmap.stx +0 -294
- package/src/ui/image/Image.stx +0 -165
- package/src/ui/image/README.md +0 -164
- package/src/ui/image/index.ts +0 -22
- package/src/ui/input/EmailInput.stx +0 -30
- package/src/ui/input/NumberInput.stx +0 -126
- package/src/ui/input/PasswordInput.stx +0 -143
- package/src/ui/input/SearchInput.stx +0 -44
- package/src/ui/input/TextInput.stx +0 -132
- package/src/ui/input/index.ts +0 -180
- package/src/ui/listbox/Listbox.stx +0 -101
- package/src/ui/listbox/ListboxButton.stx +0 -28
- package/src/ui/listbox/ListboxLabel.stx +0 -11
- package/src/ui/listbox/ListboxOption.stx +0 -49
- package/src/ui/listbox/ListboxOptions.stx +0 -16
- package/src/ui/listbox/README.md +0 -135
- package/src/ui/listbox/index.ts +0 -39
- package/src/ui/navigator/Navigator.stx +0 -113
- package/src/ui/navigator/README.md +0 -352
- package/src/ui/navigator/index.ts +0 -20
- package/src/ui/notification/Notification.stx +0 -117
- package/src/ui/notification/README.md +0 -114
- package/src/ui/notification/index.ts +0 -12
- package/src/ui/pagination/Pagination.stx +0 -150
- package/src/ui/pagination/index.ts +0 -11
- package/src/ui/payment/Checkout.stx +0 -31
- package/src/ui/payment/DefaultPaymentMethod.stx +0 -62
- package/src/ui/payment/PaymentMethods.stx +0 -104
- package/src/ui/payment/README.md +0 -345
- package/src/ui/payment/SubscriptionCheckout.stx +0 -323
- package/src/ui/payment/index.ts +0 -59
- package/src/ui/popover/Popover.stx +0 -60
- package/src/ui/popover/PopoverButton.stx +0 -29
- package/src/ui/popover/PopoverPanel.stx +0 -23
- package/src/ui/popover/README.md +0 -91
- package/src/ui/popover/index.ts +0 -22
- package/src/ui/portal/Portal.stx +0 -57
- package/src/ui/portal/index.ts +0 -6
- package/src/ui/progress/Progress.stx +0 -131
- package/src/ui/progress/index.ts +0 -12
- package/src/ui/radio/Radio.stx +0 -77
- package/src/ui/radio/index.ts +0 -15
- package/src/ui/radio-group/README.md +0 -139
- package/src/ui/radio-group/RadioGroup.stx +0 -60
- package/src/ui/radio-group/RadioGroupDescription.stx +0 -11
- package/src/ui/radio-group/RadioGroupLabel.stx +0 -11
- package/src/ui/radio-group/RadioGroupOption.stx +0 -58
- package/src/ui/radio-group/index.ts +0 -30
- package/src/ui/select/README.md +0 -180
- package/src/ui/select/Select.stx +0 -82
- package/src/ui/select/index.ts +0 -21
- package/src/ui/sidebar/Sidebar.stx +0 -476
- package/src/ui/sidebar/SidebarFooter.stx +0 -71
- package/src/ui/sidebar/SidebarHeader.stx +0 -103
- package/src/ui/sidebar/SidebarItem.stx +0 -86
- package/src/ui/sidebar/SidebarSection.stx +0 -83
- package/src/ui/sidebar/index.ts +0 -153
- package/src/ui/sidebar/themes.ts +0 -240
- package/src/ui/skeleton/Skeleton.stx +0 -48
- package/src/ui/skeleton/index.ts +0 -11
- package/src/ui/spinner/Spinner.stx +0 -109
- package/src/ui/spinner/index.ts +0 -9
- package/src/ui/stepper/README.md +0 -36
- package/src/ui/stepper/Stepper.stx +0 -17
- package/src/ui/stepper/StepperStep.stx +0 -57
- package/src/ui/stepper/index.ts +0 -19
- package/src/ui/storage/README.md +0 -193
- package/src/ui/storage/index.ts +0 -347
- package/src/ui/switch/README.md +0 -114
- package/src/ui/switch/Switch.stx +0 -72
- package/src/ui/switch/index.ts +0 -10
- package/src/ui/table/README.md +0 -39
- package/src/ui/table/Table.stx +0 -14
- package/src/ui/table/TableBody.stx +0 -10
- package/src/ui/table/TableCell.stx +0 -10
- package/src/ui/table/TableHead.stx +0 -10
- package/src/ui/table/TableHeader.stx +0 -20
- package/src/ui/table/TableRow.stx +0 -11
- package/src/ui/table/index.ts +0 -35
- package/src/ui/tabs/TabPanel.stx +0 -25
- package/src/ui/tabs/Tabs.stx +0 -202
- package/src/ui/tabs/index.ts +0 -50
- package/src/ui/teleport/Teleport.stx +0 -65
- package/src/ui/teleport/index.ts +0 -7
- package/src/ui/textarea/Textarea.stx +0 -108
- package/src/ui/textarea/index.ts +0 -20
- package/src/ui/tooltip/Tooltip.stx +0 -71
- package/src/ui/tooltip/index.ts +0 -10
- package/src/ui/transition/README.md +0 -30
- package/src/ui/transition/Transition.stx +0 -102
- package/src/ui/transition/index.ts +0 -25
- package/src/ui/video/README.md +0 -47
- package/src/ui/video/Video.stx +0 -45
- package/src/ui/video/index.ts +0 -16
- package/src/ui/virtual-list/VirtualList.stx +0 -67
- package/src/ui/virtual-list/index.ts +0 -10
- package/src/ui/virtual-table/VirtualTable.stx +0 -103
- package/src/ui/virtual-table/index.ts +0 -21
- package/src/utils/PROP_VALIDATION.md +0 -458
- package/src/utils/THEME_SYSTEM.md +0 -702
- package/src/utils/accessibility.ts +0 -841
- package/src/utils/animation/easing.ts +0 -368
- package/src/utils/animation/helpers.ts +0 -223
- package/src/utils/animation/index.ts +0 -5
- package/src/utils/animation/keyframes.ts +0 -230
- package/src/utils/error-handling.ts +0 -475
- package/src/utils/highlighter.ts +0 -93
- package/src/utils/i18n.ts +0 -634
- package/src/utils/keep-alive.ts +0 -819
- package/src/utils/lazy-loading.ts +0 -530
- package/src/utils/prop-validation.ts +0 -478
- package/src/utils/theme.ts +0 -626
package/src/ui/payment/README.md
DELETED
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
# Payment Components
|
|
2
|
-
|
|
3
|
-
Comprehensive payment components for Stripe integration, including payment method management and checkout flows.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
bun add @stacksjs/components
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Components
|
|
12
|
-
|
|
13
|
-
### Checkout
|
|
14
|
-
Main checkout wrapper component that supports both subscription and one-time payment modes.
|
|
15
|
-
|
|
16
|
-
### SubscriptionCheckout
|
|
17
|
-
Full subscription checkout flow with Stripe Elements integration.
|
|
18
|
-
|
|
19
|
-
### PaymentMethods
|
|
20
|
-
Display and manage a list of saved payment methods with delete and set-default actions.
|
|
21
|
-
|
|
22
|
-
### DefaultPaymentMethod
|
|
23
|
-
Display the default payment method with a badge.
|
|
24
|
-
|
|
25
|
-
## Usage
|
|
26
|
-
|
|
27
|
-
### Checkout Component
|
|
28
|
-
|
|
29
|
-
```stx
|
|
30
|
-
<script server>
|
|
31
|
-
const products = [
|
|
32
|
-
{
|
|
33
|
-
id: 1,
|
|
34
|
-
name: 'Premium Subscription',
|
|
35
|
-
price: 99900, // $999.00 in cents
|
|
36
|
-
images: '/images/subscription.jpg',
|
|
37
|
-
quantity: 1
|
|
38
|
-
}
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
const mode = 'subscription'
|
|
42
|
-
const stripePublicKey = 'pk_test_...'
|
|
43
|
-
const apiUrl = 'https://api.example.com'
|
|
44
|
-
const redirectUrl = '/thank-you'
|
|
45
|
-
const shipping = 4500
|
|
46
|
-
const taxes = 552
|
|
47
|
-
|
|
48
|
-
const onSubmit = (result) => {
|
|
49
|
-
if (result.success) {
|
|
50
|
-
console.log('Payment successful!')
|
|
51
|
-
window.location.href = redirectUrl
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const onRemoveProduct = (productId) => {
|
|
56
|
-
console.log('Remove product:', productId)
|
|
57
|
-
// Update cart state
|
|
58
|
-
}
|
|
59
|
-
</script>
|
|
60
|
-
|
|
61
|
-
<Checkout
|
|
62
|
-
:products="products"
|
|
63
|
-
:mode="mode"
|
|
64
|
-
:stripePublicKey="stripePublicKey"
|
|
65
|
-
:apiUrl="apiUrl"
|
|
66
|
-
:redirectUrl="redirectUrl"
|
|
67
|
-
:shipping="shipping"
|
|
68
|
-
:taxes="taxes"
|
|
69
|
-
:onSubmit="onSubmit"
|
|
70
|
-
:onRemoveProduct="onRemoveProduct"
|
|
71
|
-
/>
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### PaymentMethods Component
|
|
75
|
-
|
|
76
|
-
```stx
|
|
77
|
-
<script server>
|
|
78
|
-
const methods = [
|
|
79
|
-
{
|
|
80
|
-
id: 1,
|
|
81
|
-
brand: 'Visa',
|
|
82
|
-
last_four: '4242',
|
|
83
|
-
exp_month: 12,
|
|
84
|
-
exp_year: 2025
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
id: 2,
|
|
88
|
-
brand: 'Mastercard',
|
|
89
|
-
last_four: '5555',
|
|
90
|
-
exp_month: 6,
|
|
91
|
-
exp_year: 2026
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
|
|
95
|
-
const handleDelete = (id) => {
|
|
96
|
-
console.log('Delete payment method:', id)
|
|
97
|
-
// Call your API to delete
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const handleMakeDefault = (id) => {
|
|
101
|
-
console.log('Make default:', id)
|
|
102
|
-
// Call your API to set as default
|
|
103
|
-
}
|
|
104
|
-
</script>
|
|
105
|
-
|
|
106
|
-
<PaymentMethods
|
|
107
|
-
:paymentMethods="methods"
|
|
108
|
-
:onDeletePaymentMethod="handleDelete"
|
|
109
|
-
:onMakeDefault="handleMakeDefault"
|
|
110
|
-
/>
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### DefaultPaymentMethod Component
|
|
114
|
-
|
|
115
|
-
```stx
|
|
116
|
-
<script server>
|
|
117
|
-
const defaultMethod = {
|
|
118
|
-
id: 1,
|
|
119
|
-
brand: 'Visa',
|
|
120
|
-
last_four: '4242',
|
|
121
|
-
exp_month: 12,
|
|
122
|
-
exp_year: 2025
|
|
123
|
-
}
|
|
124
|
-
</script>
|
|
125
|
-
|
|
126
|
-
<DefaultPaymentMethod :paymentMethod="defaultMethod" />
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### SubscriptionCheckout Component
|
|
130
|
-
|
|
131
|
-
```stx
|
|
132
|
-
<script server>
|
|
133
|
-
const cartProducts = [
|
|
134
|
-
{
|
|
135
|
-
id: 1,
|
|
136
|
-
name: 'Premium Subscription',
|
|
137
|
-
price: 99900, // $999.00 in cents
|
|
138
|
-
images: '/images/subscription.jpg',
|
|
139
|
-
quantity: 1
|
|
140
|
-
}
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
const handleCheckoutComplete = (result) => {
|
|
144
|
-
if (result.success) {
|
|
145
|
-
console.log('Payment successful!')
|
|
146
|
-
// Redirect to success page
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
</script>
|
|
150
|
-
|
|
151
|
-
<SubscriptionCheckout
|
|
152
|
-
:products="cartProducts"
|
|
153
|
-
stripePublicKey="pk_test_..."
|
|
154
|
-
apiUrl="https://api.example.com/payments"
|
|
155
|
-
:shipping="4500"
|
|
156
|
-
:taxes="552"
|
|
157
|
-
:onSubmit="handleCheckoutComplete"
|
|
158
|
-
/>
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Props
|
|
162
|
-
|
|
163
|
-
### Checkout Props
|
|
164
|
-
|
|
165
|
-
| Prop | Type | Default | Description |
|
|
166
|
-
|------|------|---------|-------------|
|
|
167
|
-
| `products` | `Product[]` | `[]` | Products in cart |
|
|
168
|
-
| `mode` | `'subscription' \| 'one-time'` | `'subscription'` | Payment mode |
|
|
169
|
-
| `stripePublicKey` | `string` | - | Stripe publishable key (required) |
|
|
170
|
-
| `apiUrl` | `string` | - | Base API URL (required) |
|
|
171
|
-
| `redirectUrl` | `string` | `''` | Redirect URL after payment |
|
|
172
|
-
| `onSubmit` | `function` | - | Callback when payment completes |
|
|
173
|
-
| `onRemoveProduct` | `function` | - | Callback when product removed from cart |
|
|
174
|
-
| `shipping` | `number` | `0` | Shipping cost in cents |
|
|
175
|
-
| `taxes` | `number` | `0` | Tax amount in cents |
|
|
176
|
-
| `className` | `string` | `''` | Additional CSS classes |
|
|
177
|
-
|
|
178
|
-
### PaymentMethods Props
|
|
179
|
-
|
|
180
|
-
| Prop | Type | Default | Description |
|
|
181
|
-
|------|------|---------|-------------|
|
|
182
|
-
| `paymentMethods` | `PaymentMethod[]` | `[]` | Array of payment methods |
|
|
183
|
-
| `onDeletePaymentMethod` | `function` | - | Callback when delete clicked |
|
|
184
|
-
| `onMakeDefault` | `function` | - | Callback when make default clicked |
|
|
185
|
-
| `isLoading` | `boolean` | `false` | Show loading state |
|
|
186
|
-
| `className` | `string` | `''` | Additional CSS classes |
|
|
187
|
-
|
|
188
|
-
### DefaultPaymentMethod Props
|
|
189
|
-
|
|
190
|
-
| Prop | Type | Default | Description |
|
|
191
|
-
|------|------|---------|-------------|
|
|
192
|
-
| `paymentMethod` | `PaymentMethod \| null` | - | Default payment method or null |
|
|
193
|
-
| `isLoading` | `boolean` | `false` | Show loading state |
|
|
194
|
-
| `className` | `string` | `''` | Additional CSS classes |
|
|
195
|
-
|
|
196
|
-
### SubscriptionCheckout Props
|
|
197
|
-
|
|
198
|
-
| Prop | Type | Default | Description |
|
|
199
|
-
|------|------|---------|-------------|
|
|
200
|
-
| `products` | `Product[]` | `[]` | Products in cart |
|
|
201
|
-
| `stripePublicKey` | `string` | - | Stripe publishable key (required) |
|
|
202
|
-
| `apiUrl` | `string` | - | Base API URL (required) |
|
|
203
|
-
| `onSubmit` | `function` | - | Callback when payment completes |
|
|
204
|
-
| `onRemoveProduct` | `function` | - | Callback when product removed from cart |
|
|
205
|
-
| `shipping` | `number` | `0` | Shipping cost in cents |
|
|
206
|
-
| `taxes` | `number` | `0` | Tax amount in cents |
|
|
207
|
-
| `className` | `string` | `''` | Additional CSS classes |
|
|
208
|
-
|
|
209
|
-
## TypeScript Interfaces
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
interface PaymentMethod {
|
|
213
|
-
id: string | number
|
|
214
|
-
brand: string
|
|
215
|
-
last_four: string
|
|
216
|
-
exp_month: number
|
|
217
|
-
exp_year: number
|
|
218
|
-
is_default?: boolean
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
interface Product {
|
|
222
|
-
id: string | number
|
|
223
|
-
name: string
|
|
224
|
-
price: number // in cents
|
|
225
|
-
images?: string
|
|
226
|
-
quantity?: number
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Backend API Requirements
|
|
231
|
-
|
|
232
|
-
### Create Subscription Endpoint
|
|
233
|
-
|
|
234
|
-
Your `apiUrl` should implement a `POST /stripe/create-subscription` endpoint:
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
// Example Express.js endpoint
|
|
238
|
-
app.post('/stripe/create-subscription', async (req, res) => {
|
|
239
|
-
const { amount, quantity } = req.body
|
|
240
|
-
|
|
241
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
242
|
-
amount: amount,
|
|
243
|
-
currency: 'usd',
|
|
244
|
-
automatic_payment_methods: {
|
|
245
|
-
enabled: true,
|
|
246
|
-
},
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
res.json({
|
|
250
|
-
client_secret: paymentIntent.client_secret,
|
|
251
|
-
})
|
|
252
|
-
})
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
**Note**: The endpoint path must be `{apiUrl}/stripe/create-subscription` to match the component's expectations.
|
|
256
|
-
|
|
257
|
-
## Features
|
|
258
|
-
|
|
259
|
-
- **Stripe Elements** - Integrated Stripe payment form
|
|
260
|
-
- **Payment Management** - View, delete, set default payment methods
|
|
261
|
-
- **Dark Mode** - Full dark mode support
|
|
262
|
-
- **Loading States** - Skeleton loaders while fetching data
|
|
263
|
-
- **Empty States** - Friendly messages when no payment methods
|
|
264
|
-
- **Responsive** - Mobile-friendly layouts
|
|
265
|
-
- **Accessible** - Proper ARIA labels and keyboard navigation
|
|
266
|
-
- **Confirmation** - Delete confirmation dialogs
|
|
267
|
-
- **Error Handling** - Graceful error messages
|
|
268
|
-
- **Modern ES Modules** - Clean export syntax
|
|
269
|
-
- **Headwind Styling** - Utility-first CSS classes
|
|
270
|
-
|
|
271
|
-
## Stripe.js Integration
|
|
272
|
-
|
|
273
|
-
The `SubscriptionCheckout` component automatically loads Stripe.js from the CDN. Make sure you have a valid Stripe account and publishable key.
|
|
274
|
-
|
|
275
|
-
### Test Cards
|
|
276
|
-
|
|
277
|
-
Use these test cards in development:
|
|
278
|
-
|
|
279
|
-
- **Success**: `4242 4242 4242 4242`
|
|
280
|
-
- **Decline**: `4000 0000 0000 0002`
|
|
281
|
-
- **Requires 3DS**: `4000 0027 6000 3184`
|
|
282
|
-
|
|
283
|
-
Use any future expiration date and any 3-digit CVC.
|
|
284
|
-
|
|
285
|
-
## Example: Full Payment Flow
|
|
286
|
-
|
|
287
|
-
```stx
|
|
288
|
-
<script server>
|
|
289
|
-
// Fetch payment methods
|
|
290
|
-
const paymentMethods = await fetch('/api/payment-methods').then(r => r.json())
|
|
291
|
-
|
|
292
|
-
// Fetch default payment method
|
|
293
|
-
const defaultMethod = paymentMethods.find(m => m.is_default)
|
|
294
|
-
|
|
295
|
-
// Cart products
|
|
296
|
-
const cart = [
|
|
297
|
-
{ id: 1, name: 'Product 1', price: 5000, quantity: 2 },
|
|
298
|
-
{ id: 2, name: 'Product 2', price: 3000, quantity: 1 },
|
|
299
|
-
]
|
|
300
|
-
|
|
301
|
-
const handleDelete = async (id) => {
|
|
302
|
-
await fetch(`/api/payment-methods/${id}`, { method: 'DELETE' })
|
|
303
|
-
// Refetch payment methods
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const handleMakeDefault = async (id) => {
|
|
307
|
-
await fetch(`/api/payment-methods/${id}/default`, { method: 'POST' })
|
|
308
|
-
// Refetch payment methods
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const handleCheckout = (result) => {
|
|
312
|
-
if (result.success) {
|
|
313
|
-
window.location.href = '/thank-you'
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
</script>
|
|
317
|
-
|
|
318
|
-
<!-- Default Payment Method -->
|
|
319
|
-
<DefaultPaymentMethod :paymentMethod="defaultMethod" />
|
|
320
|
-
|
|
321
|
-
<!-- All Payment Methods -->
|
|
322
|
-
<PaymentMethods
|
|
323
|
-
:paymentMethods="paymentMethods"
|
|
324
|
-
:onDeletePaymentMethod="handleDelete"
|
|
325
|
-
:onMakeDefault="handleMakeDefault"
|
|
326
|
-
/>
|
|
327
|
-
|
|
328
|
-
<!-- Checkout Form -->
|
|
329
|
-
<SubscriptionCheckout
|
|
330
|
-
:products="cart"
|
|
331
|
-
stripePublicKey="pk_test_..."
|
|
332
|
-
apiUrl="https://api.example.com/payments"
|
|
333
|
-
:shipping="1000"
|
|
334
|
-
:taxes="800"
|
|
335
|
-
:onSubmit="handleCheckout"
|
|
336
|
-
/>
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
## Security Notes
|
|
340
|
-
|
|
341
|
-
- **Never** expose your Stripe secret key in client code
|
|
342
|
-
- Always validate payment amounts on the server
|
|
343
|
-
- Use HTTPS in production
|
|
344
|
-
- Implement proper authentication for payment management endpoints
|
|
345
|
-
- Verify webhook signatures from Stripe
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
export const products = $props.products || []
|
|
3
|
-
export const stripePublicKey = $props.stripePublicKey || ''
|
|
4
|
-
export const apiUrl = $props.apiUrl || ''
|
|
5
|
-
export const showRemoveProduct = $props.showRemoveProduct ?? true
|
|
6
|
-
export const shipping = $props.shipping || 0
|
|
7
|
-
export const taxes = $props.taxes || 0
|
|
8
|
-
export const className = $props.className || ''
|
|
9
|
-
|
|
10
|
-
export const subtotal = products.reduce((sum, product) => sum + (product.price * (product.quantity || 1)), 0)
|
|
11
|
-
export const total = subtotal + shipping + taxes
|
|
12
|
-
|
|
13
|
-
export const containerClasses = `bg-gray-50 dark:bg-blue-gray-900 ${className}`.trim()
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<script client>
|
|
17
|
-
const emit = defineEmits()
|
|
18
|
-
const stripePublicKey = {{ stripePublicKey }}
|
|
19
|
-
const apiUrl = {{ apiUrl }}
|
|
20
|
-
const products = {{ products }}
|
|
21
|
-
const total = {{ total }}
|
|
22
|
-
|
|
23
|
-
let stripe = null
|
|
24
|
-
let elements = null
|
|
25
|
-
let paymentElement = null
|
|
26
|
-
|
|
27
|
-
function onRemoveProduct(productId) {
|
|
28
|
-
emit('removeProduct', productId)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function setupStripeElements() {
|
|
32
|
-
if (!stripe || !apiUrl) return
|
|
33
|
-
try {
|
|
34
|
-
const response = await fetch(`${apiUrl}/stripe/create-subscription`, {
|
|
35
|
-
method: 'POST',
|
|
36
|
-
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
37
|
-
body: JSON.stringify({
|
|
38
|
-
amount: total,
|
|
39
|
-
quantity: products.reduce((sum, p) => sum + (p.quantity || 1), 0),
|
|
40
|
-
}),
|
|
41
|
-
})
|
|
42
|
-
const paymentIntent = await response.json()
|
|
43
|
-
const { clientSecret } = paymentIntent
|
|
44
|
-
if (!clientSecret) {
|
|
45
|
-
console.error('No client secret returned from API')
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
elements = stripe.elements({
|
|
49
|
-
clientSecret,
|
|
50
|
-
appearance: {
|
|
51
|
-
theme: document.documentElement.classList.contains('dark') ? 'night' : 'stripe',
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
paymentElement = elements.create('payment')
|
|
55
|
-
paymentElement.mount('#payment-element')
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
console.error('Error setting up Stripe elements:', error)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function initializeStripe() {
|
|
63
|
-
if (!stripePublicKey) {
|
|
64
|
-
console.error('Stripe public key is required')
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
if (window.Stripe) {
|
|
68
|
-
stripe = window.Stripe(stripePublicKey)
|
|
69
|
-
await setupStripeElements()
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
const script = document.createElement('script')
|
|
73
|
-
script.src = 'https://js.stripe.com/v3/'
|
|
74
|
-
script.async = true
|
|
75
|
-
script.onload = async () => {
|
|
76
|
-
stripe = window.Stripe(stripePublicKey)
|
|
77
|
-
await setupStripeElements()
|
|
78
|
-
}
|
|
79
|
-
document.head.appendChild(script)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function onSubmit(event) {
|
|
83
|
-
event.preventDefault()
|
|
84
|
-
if (!stripe || !elements) {
|
|
85
|
-
emit('error', { message: 'Stripe not initialized' })
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
const { error } = await stripe.confirmPayment({
|
|
90
|
-
elements,
|
|
91
|
-
redirect: 'if_required',
|
|
92
|
-
confirmParams: { return_url: window.location.href },
|
|
93
|
-
})
|
|
94
|
-
if (error) {
|
|
95
|
-
emit('error', error)
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
emit('submit', { success: true })
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
emit('error', error)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
onMount(() => initializeStripe())
|
|
107
|
-
</script>
|
|
108
|
-
|
|
109
|
-
<div class="{{ containerClasses }}">
|
|
110
|
-
<div class="mx-auto max-w-2xl px-4 pb-24 pt-16 lg:max-w-7xl lg:px-8 sm:px-6">
|
|
111
|
-
<h2 class="sr-only">Checkout</h2>
|
|
112
|
-
|
|
113
|
-
<form class="lg:grid lg:grid-cols-2 lg:gap-x-12 xl:gap-x-16" @submit="onSubmit($event)">
|
|
114
|
-
<!-- Left Column - Contact & Payment -->
|
|
115
|
-
<div>
|
|
116
|
-
<!-- Contact Information -->
|
|
117
|
-
<div>
|
|
118
|
-
<h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
|
|
119
|
-
Contact information
|
|
120
|
-
</h2>
|
|
121
|
-
|
|
122
|
-
<div class="mt-4">
|
|
123
|
-
<label for="email-address" class="block text-sm text-gray-700 dark:text-gray-300 font-medium">
|
|
124
|
-
Email address
|
|
125
|
-
</label>
|
|
126
|
-
<div class="mt-1">
|
|
127
|
-
<input
|
|
128
|
-
id="email-address"
|
|
129
|
-
type="email"
|
|
130
|
-
name="email-address"
|
|
131
|
-
autocomplete="email"
|
|
132
|
-
required
|
|
133
|
-
class="block w-full border border-gray-300 dark:border-gray-600 rounded-md p-2 shadow-sm bg-white dark:bg-blue-gray-700 text-gray-900 dark:text-gray-100 focus:border-indigo-500 dark:focus:border-indigo-400 sm:text-sm focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
134
|
-
/>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<!-- Billing Information -->
|
|
140
|
-
<div class="mt-10 border-t border-gray-200 dark:border-gray-700 pt-10">
|
|
141
|
-
<h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
|
|
142
|
-
Billing information
|
|
143
|
-
</h2>
|
|
144
|
-
|
|
145
|
-
<div class="mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
|
146
|
-
<div class="sm:col-span-2">
|
|
147
|
-
<label for="card-name" class="block text-sm text-gray-700 dark:text-gray-300 font-medium">
|
|
148
|
-
Name on card
|
|
149
|
-
</label>
|
|
150
|
-
<div class="mt-1">
|
|
151
|
-
<input
|
|
152
|
-
type="text"
|
|
153
|
-
id="card-name"
|
|
154
|
-
name="card-name"
|
|
155
|
-
autocomplete="cc-name"
|
|
156
|
-
required
|
|
157
|
-
class="block w-full border border-gray-300 dark:border-gray-600 rounded-md p-2 shadow-sm bg-white dark:bg-blue-gray-700 text-gray-900 dark:text-gray-100 focus:border-indigo-500 sm:text-sm focus:ring-indigo-500"
|
|
158
|
-
/>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<!-- Stripe Element Mount Point -->
|
|
163
|
-
<div class="sm:col-span-2">
|
|
164
|
-
<div id="payment-element" class="mt-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-blue-gray-700 p-3"></div>
|
|
165
|
-
</div>
|
|
166
|
-
</div>
|
|
167
|
-
</div>
|
|
168
|
-
|
|
169
|
-
<!-- Payment Type -->
|
|
170
|
-
<div class="mt-10 border-t border-gray-200 dark:border-gray-700 pt-10">
|
|
171
|
-
<h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
|
|
172
|
-
Payment
|
|
173
|
-
</h2>
|
|
174
|
-
|
|
175
|
-
<fieldset class="mt-4">
|
|
176
|
-
<legend class="sr-only">Payment type</legend>
|
|
177
|
-
<div class="sm:flex sm:items-center space-y-4 sm:space-x-10 sm:space-y-0">
|
|
178
|
-
<div class="flex items-center">
|
|
179
|
-
<input
|
|
180
|
-
id="credit-card"
|
|
181
|
-
name="payment-type"
|
|
182
|
-
type="radio"
|
|
183
|
-
checked
|
|
184
|
-
class="h-4 w-4 border-gray-300 dark:border-gray-600 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
185
|
-
/>
|
|
186
|
-
<label for="credit-card" class="ml-3 block text-sm text-gray-700 dark:text-gray-300 font-medium">
|
|
187
|
-
Credit card
|
|
188
|
-
</label>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
</fieldset>
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
|
|
195
|
-
<!-- Right Column - Order Summary -->
|
|
196
|
-
<div class="mt-10 lg:mt-0">
|
|
197
|
-
<h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
|
|
198
|
-
Order summary
|
|
199
|
-
</h2>
|
|
200
|
-
|
|
201
|
-
<div class="mt-4 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-blue-gray-800 shadow-sm">
|
|
202
|
-
<h3 class="sr-only">Items in your cart</h3>
|
|
203
|
-
|
|
204
|
-
<!-- Product List -->
|
|
205
|
-
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
206
|
-
@foreach(product in products)
|
|
207
|
-
<li class="flex px-4 py-6 sm:px-6">
|
|
208
|
-
<div class="flex-shrink-0">
|
|
209
|
-
@if(product.images)
|
|
210
|
-
<img src="{{ product.images }}" alt="{{ product.name }}" class="w-20 rounded-md" />
|
|
211
|
-
@else
|
|
212
|
-
<div class="w-20 h-20 bg-gray-200 dark:bg-gray-700 rounded-md flex items-center justify-center">
|
|
213
|
-
<svg class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
214
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
215
|
-
</svg>
|
|
216
|
-
</div>
|
|
217
|
-
@endif
|
|
218
|
-
</div>
|
|
219
|
-
|
|
220
|
-
<div class="ml-6 flex flex-1 flex-col">
|
|
221
|
-
<div class="flex">
|
|
222
|
-
<div class="min-w-0 flex-1">
|
|
223
|
-
<h4 class="text-sm">
|
|
224
|
-
<a href="#" class="text-gray-700 dark:text-gray-300 font-medium hover:text-gray-800 dark:hover:text-gray-200">
|
|
225
|
-
{{ product.name }}
|
|
226
|
-
</a>
|
|
227
|
-
</h4>
|
|
228
|
-
</div>
|
|
229
|
-
|
|
230
|
-
@if(showRemoveProduct)
|
|
231
|
-
<div class="ml-4 flow-root flex-shrink-0">
|
|
232
|
-
<button
|
|
233
|
-
type="button"
|
|
234
|
-
class="flex items-center justify-center bg-white dark:bg-blue-gray-700 p-2.5 text-gray-400 dark:text-gray-500 -m-2.5 hover:text-gray-500 dark:hover:text-gray-400"
|
|
235
|
-
@click="onRemoveProduct('{{ product.id }}')"
|
|
236
|
-
title="Remove"
|
|
237
|
-
>
|
|
238
|
-
<span class="sr-only">Remove</span>
|
|
239
|
-
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
240
|
-
<path fill-rule="evenodd" d="M8.75 1A2.75 2.75 0 0 0 6 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 1 0 .23 1.482l.149-.022.841 10.518A2.75 2.75 0 0 0 7.596 19h4.807a2.75 2.75 0 0 0 2.742-2.53l.841-10.52.149.023a.75.75 0 0 0 .23-1.482A41.03 41.03 0 0 0 14 4.193V3.75A2.75 2.75 0 0 0 11.25 1h-2.5ZM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4ZM8.58 7.72a.75.75 0 0 0-1.5.06l.3 7.5a.75.75 0 1 0 1.5-.06l-.3-7.5Zm4.34.06a.75.75 0 1 0-1.5-.06l-.3 7.5a.75.75 0 1 0 1.5.06l.3-7.5Z" clip-rule="evenodd" />
|
|
241
|
-
</svg>
|
|
242
|
-
</button>
|
|
243
|
-
</div>
|
|
244
|
-
@endif
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
<div class="flex flex-1 items-end justify-between pt-2">
|
|
248
|
-
<p class="mt-1 text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
249
|
-
${{ (product.price / 100).toFixed(2) }}
|
|
250
|
-
</p>
|
|
251
|
-
|
|
252
|
-
<div class="ml-4">
|
|
253
|
-
<label for="quantity-{{ product.id }}" class="sr-only">Quantity</label>
|
|
254
|
-
<select
|
|
255
|
-
id="quantity-{{ product.id }}"
|
|
256
|
-
name="quantity"
|
|
257
|
-
class="border border-gray-300 dark:border-gray-600 rounded-md text-left text-base text-gray-700 dark:text-gray-300 font-medium shadow-sm bg-white dark:bg-blue-gray-700 focus:border-indigo-500 sm:text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
258
|
-
>
|
|
259
|
-
<option value="1" {{ (product.quantity || 1) === 1 ? 'selected' : '' }}>1</option>
|
|
260
|
-
<option value="2" {{ (product.quantity || 1) === 2 ? 'selected' : '' }}>2</option>
|
|
261
|
-
<option value="3" {{ (product.quantity || 1) === 3 ? 'selected' : '' }}>3</option>
|
|
262
|
-
<option value="4" {{ (product.quantity || 1) === 4 ? 'selected' : '' }}>4</option>
|
|
263
|
-
<option value="5" {{ (product.quantity || 1) === 5 ? 'selected' : '' }}>5</option>
|
|
264
|
-
<option value="6" {{ (product.quantity || 1) === 6 ? 'selected' : '' }}>6</option>
|
|
265
|
-
<option value="7" {{ (product.quantity || 1) === 7 ? 'selected' : '' }}>7</option>
|
|
266
|
-
<option value="8" {{ (product.quantity || 1) === 8 ? 'selected' : '' }}>8</option>
|
|
267
|
-
</select>
|
|
268
|
-
</div>
|
|
269
|
-
</div>
|
|
270
|
-
</div>
|
|
271
|
-
</li>
|
|
272
|
-
@endforeach
|
|
273
|
-
</ul>
|
|
274
|
-
|
|
275
|
-
<!-- Summary Totals -->
|
|
276
|
-
<dl class="border-t border-gray-200 dark:border-gray-700 px-4 py-6 space-y-6 sm:px-6">
|
|
277
|
-
<div class="flex items-center justify-between">
|
|
278
|
-
<dt class="text-sm text-gray-600 dark:text-gray-400">Subtotal</dt>
|
|
279
|
-
<dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
280
|
-
${{ (subtotal / 100).toFixed(2) }}
|
|
281
|
-
</dd>
|
|
282
|
-
</div>
|
|
283
|
-
|
|
284
|
-
@if(shipping > 0)
|
|
285
|
-
<div class="flex items-center justify-between">
|
|
286
|
-
<dt class="text-sm text-gray-600 dark:text-gray-400">Shipping</dt>
|
|
287
|
-
<dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
288
|
-
${{ (shipping / 100).toFixed(2) }}
|
|
289
|
-
</dd>
|
|
290
|
-
</div>
|
|
291
|
-
@endif
|
|
292
|
-
|
|
293
|
-
@if(taxes > 0)
|
|
294
|
-
<div class="flex items-center justify-between">
|
|
295
|
-
<dt class="text-sm text-gray-600 dark:text-gray-400">Taxes</dt>
|
|
296
|
-
<dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
297
|
-
${{ (taxes / 100).toFixed(2) }}
|
|
298
|
-
</dd>
|
|
299
|
-
</div>
|
|
300
|
-
@endif
|
|
301
|
-
|
|
302
|
-
<div class="flex items-center justify-between border-t border-gray-200 dark:border-gray-700 pt-6">
|
|
303
|
-
<dt class="text-base text-gray-900 dark:text-gray-100 font-medium">Total</dt>
|
|
304
|
-
<dd class="text-base text-gray-900 dark:text-gray-100 font-medium">
|
|
305
|
-
${{ (total / 100).toFixed(2) }}
|
|
306
|
-
</dd>
|
|
307
|
-
</div>
|
|
308
|
-
</dl>
|
|
309
|
-
|
|
310
|
-
<!-- Submit Button -->
|
|
311
|
-
<div class="border-t border-gray-200 dark:border-gray-700 px-4 py-6 sm:px-6">
|
|
312
|
-
<button
|
|
313
|
-
type="submit"
|
|
314
|
-
class="w-full border border-transparent rounded-md bg-indigo-600 dark:bg-indigo-500 px-4 py-3 text-base text-white font-medium shadow-sm hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 focus:ring-offset-gray-50 dark:focus:ring-offset-gray-900"
|
|
315
|
-
>
|
|
316
|
-
Confirm order
|
|
317
|
-
</button>
|
|
318
|
-
</div>
|
|
319
|
-
</div>
|
|
320
|
-
</div>
|
|
321
|
-
</form>
|
|
322
|
-
</div>
|
|
323
|
-
</div>
|