Skip to content

Commit b02d6e9

Browse files
chore: wip
1 parent 67f2f9a commit b02d6e9

File tree

10 files changed

+258
-87
lines changed

10 files changed

+258
-87
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { RequestInstance } from '@stacksjs/types'
2+
import { Action } from '@stacksjs/actions'
3+
import { stripe } from '@stacksjs/payments'
4+
5+
export default new Action({
6+
name: 'CreatePaymentIntentAction',
7+
description: 'Create Payment Intent for stripe',
8+
method: 'POST',
9+
async handle(request: RequestInstance) {
10+
const amount = Number(request.get('amount'))
11+
12+
const paymentIntent = await stripe.paymentIntent.create({
13+
amount,
14+
currency: 'usd',
15+
})
16+
17+
return paymentIntent
18+
},
19+
})

bun.lockb

10.8 KB
Binary file not shown.

routes/api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ route.get('/install', 'Actions/InstallAction')
2525
route.post('/ai/ask', 'Actions/AI/AskAction')
2626
route.post('/ai/summary', 'Actions/AI/SummaryAction')
2727

28+
route.post('/create-payment-intent', 'Actions/Payment/CreatePaymentIntentAction')
29+
30+
31+
2832
// route.group('/some-path', async () => {...})
2933
// route.action('/example') // equivalent to `route.get('/example', 'ExampleAction')`
3034
// route.action('Dashboard/GetProjects')

storage/framework/core/components/stripe/index.html

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@
55
<link rel="icon" type="image/svg+xml" href="/assest/stacks.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Stacks</title>
8-
<!-- Add this in your index.html within the <head> tag -->
9-
<link rel="stylesheet" href="https://js.stripe.com/v3/">
108
</head>
119
<body>
1210
<div id="app"></div>
1311

1412
<!-- Add this in your index.html -->
15-
<script src="https://js.stripe.com/v3/"></script>
1613
<script type="module" src="/src/main.ts"></script>
1714
</body>
1815
</html>

storage/framework/core/components/stripe/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
"preview": "bunx --bun vite preview"
3535
},
3636
"dependencies": {
37+
"@julr/unocss-preset-forms": "^0.1.0",
3738
"@stacksjs/ui": "workspace:*",
39+
"@unocss/reset": "0.61.0",
3840
"unocss": "0.61.0",
3941
"vue": "^3.5.12"
4042
},
Lines changed: 226 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,246 @@
11
<script setup>
22
import { onBeforeUnmount, onMounted, ref } from 'vue'
33
4+
import { loadStripe } from "@stripe/stripe-js";
5+
6+
let stripe
7+
let elements
8+
49
const product = ref({
510
name: 'iPhone Pro Max',
611
price: 99900, // Price in cents ($999.00)
712
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',
813
})
914
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 = ''
1418
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);
2020
21-
onBeforeUnmount(() => {
22-
if (card) {
23-
card.unmount()
24-
}
25-
})
21+
const url = 'http://localhost:3008/create-payment-intent'
2622
27-
// eslint-disable-next-line unused-imports/no-unused-vars
28-
const loading = ref(false)
23+
const body = { amount: 999 }
2924
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+
})
3643
</script>
3744

3845
<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>
55101

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>
61163
</div>
62164

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&#039;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>
74244
</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>

storage/framework/core/components/stripe/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { createHead } from '@vueuse/head'
22
import { createApp } from 'vue'
33
import App from './App.vue'
4-
import '@unocss/reset/tailwind.css'
54
import 'uno.css'
5+
import '@unocss/reset/tailwind.css'
66

77
const app = createApp(App)
88
const head = createHead()

0 commit comments

Comments
 (0)