1
1
<script setup>
2
2
import { onBeforeUnmount , onMounted , ref } from ' vue'
3
3
4
+ import { loadStripe } from " @stripe/stripe-js" ;
5
+
6
+ let stripe
7
+ let elements
8
+
4
9
const product = ref ({
5
10
name: ' iPhone Pro Max' ,
6
11
price: 99900 , // Price in cents ($999.00)
7
12
images: ' https://store.storeimages.cdn-apple.com/1/as-images.apple.com/is/iphone-16-pro-model-unselect-gallery-2-202409?wid=5120&hei=2880&fmt=webp&qlt=70&.v=aWs5czA5aDFXU0FlMGFGRlpYRXk2UWFRQXQ2R0JQTk5udUZxTkR3ZVlpTDUwMGlYMEhQSTVNdkRDMFExUU1KbTBoUVhuTWlrY2hIK090ZGZZbk9HeE1xUVVnSHY5eU9CcGxDMkFhalkvT0FmZ0ROUGFSR25aOE5EM2xONlRwa09mbW94YnYxc1YvNXZ4emJGL0IxNFp3&traceId=1' ,
8
13
})
9
14
10
- const stripe = Stripe (' test' ) // Replace with your actual publishable key
11
- const elements = stripe .elements ()
12
- const cardElement = ref (null )
13
- let card
15
+ onMounted (async () => {
16
+ const publicKey = ' '
17
+ const clientSecret = ' '
14
18
15
- onMounted (() => {
16
- // Create an instance of the card Element and mount it
17
- card = elements .create (' card' )
18
- card .mount (cardElement .value )
19
- })
19
+ stripe = await loadStripe (publicKey);
20
20
21
- onBeforeUnmount (() => {
22
- if (card) {
23
- card .unmount ()
24
- }
25
- })
21
+ const url = ' http://localhost:3008/create-payment-intent'
26
22
27
- // eslint-disable-next-line unused-imports/no-unused-vars
28
- const loading = ref (false )
23
+ const body = { amount: 999 }
29
24
30
- // function handleSubmit() {
31
- // loading.value = true
32
- // // Placeholder for future submission logic
33
- // console.log('Payment submitted, but no backend logic yet.')
34
- // loading.value = false
35
- // }
25
+ const response = await fetch (url, {
26
+ method: ' POST' ,
27
+ headers: {
28
+ ' Content-Type' : ' application/json' ,
29
+ ' Accept' : ' application/json' ,
30
+ },
31
+ body: JSON .stringify (body),
32
+ })
33
+
34
+ const paymentIntent = await response .json ()
35
+
36
+ elements = stripe .elements ({ clientSecret: paymentIntent .client_secret });
37
+ const paymentElement = elements .create (' payment' );
38
+ paymentElement .mount (" #payment-element" );
39
+ // const linkAuthenticationElement = elements.create("linkAuthentication");
40
+ // linkAuthenticationElement.mount("#link-authentication-element");
41
+ // isLoading.value = false;
42
+ })
36
43
</script >
37
44
38
45
<template >
39
- <div class =" min-h-screen flex flex-col items-center justify-center bg-gray-100 p-4" >
40
- <div class =" max-w-xl w-full rounded-lg bg-white p-6 shadow-lg" >
41
- <!-- Wider container -->
42
- <h1 class =" mb-4 text-center text-3xl font-bold" >
43
- Checkout
44
- </h1 > <!-- Increased font size -->
45
- <div class =" mb-6" >
46
- <h2 class =" mb-2 text-2xl font-semibold" >
47
- {{ product?.name }}
48
- </h2 >
49
- <img :src =" product?.images" alt =" Product Image" class =" mb-4 h-60 w-full rounded-md object-cover" > <!-- Increased image height -->
50
- <p class =" mb-2 text-gray-700" >
51
- {{ product?.description }}
52
- </p >
53
- <span class =" text-lg font-semibold" >${{ (product?.price / 100).toFixed(2) }}</span >
54
- </div >
46
+ <div class =" bg-gray-50" >
47
+ <div class =" mx-auto max-w-2xl px-4 pb-24 pt-16 sm:px-6 lg:max-w-7xl lg:px-8" >
48
+ <h2 class =" sr-only" >Checkout</h2 >
49
+
50
+ <form class =" lg:grid lg:grid-cols-2 lg:gap-x-12 xl:gap-x-16" >
51
+ <div >
52
+ <div >
53
+ <h2 class =" text-lg font-medium text-gray-900" >Contact information</h2 >
54
+
55
+ <div class =" mt-4" >
56
+ <label for =" email-address" class =" block text-sm font-medium text-gray-700" >Email address</label >
57
+ <div class =" mt-1" >
58
+ <input type =" email" id =" email-address" name =" email-address" autocomplete =" email" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
59
+ </div >
60
+ </div >
61
+ </div >
62
+
63
+ <div class =" mt-10 border-t border-gray-200 pt-10" >
64
+ <h2 class =" text-lg font-medium text-gray-900" >Shipping information</h2 >
65
+
66
+ <div class =" mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4" >
67
+ <div >
68
+ <label for =" first-name" class =" block text-sm font-medium text-gray-700" >First name</label >
69
+ <div class =" mt-1" >
70
+ <input type =" text" id =" first-name" name =" first-name" autocomplete =" given-name" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
71
+ </div >
72
+ </div >
73
+
74
+ <div >
75
+ <label for =" last-name" class =" block text-sm font-medium text-gray-700" >Last name</label >
76
+ <div class =" mt-1" >
77
+ <input type =" text" id =" last-name" name =" last-name" autocomplete =" family-name" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
78
+ </div >
79
+ </div >
80
+
81
+ <div class =" sm:col-span-2" >
82
+ <label for =" company" class =" block text-sm font-medium text-gray-700" >Company</label >
83
+ <div class =" mt-1" >
84
+ <input type =" text" name =" company" id =" company" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
85
+ </div >
86
+ </div >
87
+
88
+ <div class =" sm:col-span-2" >
89
+ <label for =" address" class =" block text-sm font-medium text-gray-700" >Address</label >
90
+ <div class =" mt-1" >
91
+ <input type =" text" name =" address" id =" address" autocomplete =" street-address" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
92
+ </div >
93
+ </div >
94
+
95
+ <div class =" sm:col-span-2" >
96
+ <label for =" apartment" class =" block text-sm font-medium text-gray-700" >Apartment, suite, etc.</label >
97
+ <div class =" mt-1" >
98
+ <input type =" text" name =" apartment" id =" apartment" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
99
+ </div >
100
+ </div >
55
101
56
- <div class =" mb-6" >
57
- <h3 class =" mb-2 text-lg font-semibold" >
58
- Payment Information
59
- </h3 >
60
- <div ref =" cardElement" class =" border rounded-md bg-gray-50 p-3" />
102
+ <div >
103
+ <label for =" city" class =" block text-sm font-medium text-gray-700" >City</label >
104
+ <div class =" mt-1" >
105
+ <input type =" text" name =" city" id =" city" autocomplete =" address-level2" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
106
+ </div >
107
+ </div >
108
+
109
+ <div >
110
+ <label for =" country" class =" block text-sm font-medium text-gray-700" >Country</label >
111
+ <div class =" mt-1" >
112
+ <select id =" country" name =" country" autocomplete =" country-name" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
113
+ <option >United States</option >
114
+ <option >Canada</option >
115
+ <option >Mexico</option >
116
+ </select >
117
+ </div >
118
+ </div >
119
+
120
+ <div >
121
+ <label for =" region" class =" block text-sm font-medium text-gray-700" >State / Province</label >
122
+ <div class =" mt-1" >
123
+ <input type =" text" name =" region" id =" region" autocomplete =" address-level1" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
124
+ </div >
125
+ </div >
126
+
127
+ <div >
128
+ <label for =" postal-code" class =" block text-sm font-medium text-gray-700" >Postal code</label >
129
+ <div class =" mt-1" >
130
+ <input type =" text" name =" postal-code" id =" postal-code" autocomplete =" postal-code" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
131
+ </div >
132
+ </div >
133
+
134
+ <div class =" sm:col-span-2" >
135
+ <label for =" phone" class =" block text-sm font-medium text-gray-700" >Phone</label >
136
+ <div class =" mt-1" >
137
+ <input type =" text" name =" phone" id =" phone" autocomplete =" tel" class =" block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border" >
138
+ </div >
139
+ </div >
140
+ </div >
141
+ </div >
142
+
143
+
144
+ <!-- Payment -->
145
+ <div class =" mt-10 border-t border-gray-200 pt-10" >
146
+ <h2 class =" text-lg font-medium text-gray-900" >Payment</h2 >
147
+
148
+ <fieldset class =" mt-4" >
149
+ <legend class =" sr-only" >Payment type</legend >
150
+ <div class =" space-y-4 sm:flex sm:items-center sm:space-x-10 sm:space-y-0" >
151
+ <div class =" flex items-center" >
152
+ <input id =" credit-card" name =" payment-type" type =" radio" checked class =" h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-500 form" >
153
+ <label for =" credit-card" class =" ml-3 block text-sm font-medium text-gray-700" >Credit card</label >
154
+ </div >
155
+
156
+ </div >
157
+ </fieldset >
158
+
159
+ <div class =" mt-6" >
160
+ <div id =" payment-element" class =" border rounded-md bg-gray-50 p-3" />
161
+ </div >
162
+ </div >
61
163
</div >
62
164
63
- <button
64
- class =" w-full rounded-md bg-blue-600 py-3 text-white font-semibold transition hover:bg-blue-700"
65
- @click =" handlePayment"
66
- >
67
- Pay Now
68
- </button >
69
-
70
- <p class =" mt-4 text-center text-sm text-gray-500" >
71
- Secure checkout with Stripe
72
- </p >
73
- </div >
165
+ <!-- Order summary -->
166
+ <div class =" mt-10 lg:mt-0" >
167
+ <h2 class =" text-lg font-medium text-gray-900" >Order summary</h2 >
168
+
169
+ <div class =" mt-4 rounded-lg border border-gray-200 bg-white shadow-sm" >
170
+ <h3 class =" sr-only" >Items in your cart</h3 >
171
+ <ul role =" list" class =" divide-y divide-gray-200" >
172
+ <li class =" flex px-4 py-6 sm:px-6" >
173
+ <div class =" flex-shrink-0" >
174
+ <img :src =" product?.images" alt =" Front of men' ; s Basic Tee in black." class =" w-50 rounded-md" >
175
+ </div >
176
+
177
+ <div class =" ml-6 flex flex-1 flex-col" >
178
+ <div class =" flex" >
179
+ <div class =" min-w-0 flex-1" >
180
+ <h4 class =" text-sm" >
181
+ <a href =" #" class =" font-medium text-gray-700 hover:text-gray-800" >{{ product?.name }}</a >
182
+ </h4 >
183
+ <p class =" mt-1 text-sm text-gray-500" >..</p >
184
+ <p class =" mt-1 text-sm text-gray-500" >..</p >
185
+ </div >
186
+
187
+ <div class =" ml-4 flow-root flex-shrink-0" >
188
+ <button type =" button" class =" -m-2.5 flex items-center justify-center bg-white p-2.5 text-gray-400 hover:text-gray-500" >
189
+ <span class =" sr-only" >Remove</span >
190
+ <svg class =" h-5 w-5" viewBox =" 0 0 20 20" fill =" currentColor" aria-hidden =" true" data-slot =" icon" >
191
+ <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" />
192
+ </svg >
193
+ </button >
194
+ </div >
195
+ </div >
196
+
197
+ <div class =" flex flex-1 items-end justify-between pt-2" >
198
+ <p class =" mt-1 text-sm font-medium text-gray-900" >${{ (product?.price / 100).toFixed(2) }}</p >
199
+
200
+ <div class =" ml-4" >
201
+ <label for =" quantity" class =" sr-only" >Quantity</label >
202
+ <select id =" quantity" name =" quantity" class =" rounded-md border border-gray-300 text-left text-base font-medium text-gray-700 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" >
203
+ <option value =" 1" >1</option >
204
+ <option value =" 2" >2</option >
205
+ <option value =" 3" >3</option >
206
+ <option value =" 4" >4</option >
207
+ <option value =" 5" >5</option >
208
+ <option value =" 6" >6</option >
209
+ <option value =" 7" >7</option >
210
+ <option value =" 8" >8</option >
211
+ </select >
212
+ </div >
213
+ </div >
214
+ </div >
215
+ </li >
216
+
217
+ <!-- More products... -->
218
+ </ul >
219
+ <dl class =" space-y-6 border-t border-gray-200 px-4 py-6 sm:px-6" >
220
+ <div class =" flex items-center justify-between" >
221
+ <dt class =" text-sm" >Subtotal</dt >
222
+ <dd class =" text-sm font-medium text-gray-900" >$950.00</dd >
223
+ </div >
224
+ <div class =" flex items-center justify-between" >
225
+ <dt class =" text-sm" >Shipping</dt >
226
+ <dd class =" text-sm font-medium text-gray-900" >$45.00</dd >
227
+ </div >
228
+ <div class =" flex items-center justify-between" >
229
+ <dt class =" text-sm" >Taxes</dt >
230
+ <dd class =" text-sm font-medium text-gray-900" >$5.52</dd >
231
+ </div >
232
+ <div class =" flex items-center justify-between border-t border-gray-200 pt-6" >
233
+ <dt class =" text-base font-medium" >Total</dt >
234
+ <dd class =" text-base font-medium text-gray-900" >${{ (product?.price / 100).toFixed(2) }}</dd >
235
+ </div >
236
+ </dl >
237
+
238
+ <div class =" border-t border-gray-200 px-4 py-6 sm:px-6" >
239
+ <button type =" submit" class =" w-full rounded-md border border-transparent bg-indigo-600 px-4 py-3 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-50" >Confirm order</button >
240
+ </div >
241
+ </div >
242
+ </div >
243
+ </form >
74
244
</div >
75
- </template >
76
-
77
- <style scoped>
78
- /* Style for Stripe Elements */
79
- .StripeElement {
80
- box-sizing : border-box ; /* Ensure padding is included in width */
81
- height : 40px ; /* Height of the input */
82
- padding : 10px ; /* Padding inside input */
83
- border : 1px solid #ccc ; /* Light border */
84
- border-radius : 4px ; /* Rounded corners */
85
- background-color : #f9f9f9 ; /* Light background */
86
- font-size : 16px ; /* Font size */
87
- transition : border-color 0.2s ease ; /* Smooth border transition */
88
- }
89
-
90
- .StripeElement--focus {
91
- border-color : #007bff ; /* Blue border on focus */
92
- }
93
-
94
- .StripeElement--invalid {
95
- border-color : #fa755a ; /* Red border for invalid input */
96
- }
97
-
98
- .StripeElement--webkit-autofill {
99
- background-color : #f9f9f9 !important ; /* Autofill background color */
100
- }
101
- </style >
245
+ </div >
246
+ </template >
0 commit comments