Skip to content

Commit 05c1edf

Browse files
chore: wip
1 parent b2fd308 commit 05c1edf

File tree

13 files changed

+209
-44
lines changed

13 files changed

+209
-44
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Action } from '@stacksjs/actions'
2+
import User from '../../../storage/framework/orm/src/models/User.ts'
3+
4+
export default new Action({
5+
name: 'FetchDefaultPaymentMethodAction',
6+
description: 'Fetch the users default payment method',
7+
method: 'GET',
8+
async handle() {
9+
const user = await User.find(1)
10+
11+
const paymentMethod = await user?.defaultPaymentMethod()
12+
13+
return paymentMethod
14+
},
15+
})

bun.lockb

360 Bytes
Binary file not shown.

public/images/logos/amex.jpg

162 KB
Loading
File renamed without changes.

resources/stores/payment.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,58 @@
11
const apiUrl = `http://localhost:3008`
22

3+
interface StripePaymentMethod {
4+
id: string
5+
object: string
6+
allow_redisplay: string
7+
billing_details: {
8+
address: {
9+
city: string | null
10+
country: string | null
11+
line1: string | null
12+
line2: string | null
13+
postal_code: string | null
14+
state: string | null
15+
}
16+
email: string | null
17+
name: string | null
18+
phone: string | null
19+
}
20+
card: {
21+
brand: string
22+
checks: {
23+
address_line1_check: string | null
24+
address_postal_code_check: string | null
25+
cvc_check: string
26+
}
27+
country: string
28+
display_brand: string
29+
exp_month: number
30+
exp_year: number
31+
fingerprint: string
32+
funding: string
33+
generated_from: string | null
34+
last4: string
35+
networks: {
36+
available: string[]
37+
preferred: string | null
38+
}
39+
three_d_secure_usage: {
40+
supported: boolean
41+
}
42+
wallet: string | null
43+
}
44+
created: number
45+
customer: string
46+
livemode: boolean
47+
metadata: Record<string, string>
48+
type: string
49+
}
50+
351
export const usePaymentStore = defineStore('payment', {
452
state: (): any => {
553
return {
6-
paymentMethods: [] as any[]
54+
paymentMethods: [] as StripePaymentMethod[],
55+
defaultPaymentMethod: {} as StripePaymentMethod,
756
}
857
},
958

@@ -15,16 +64,31 @@ export const usePaymentStore = defineStore('payment', {
1564
'Content-Type': 'application/json',
1665
'Accept': 'application/json',
1766
},
18-
}).then((res) => res.json())
67+
}).then(res => res.json())
1968

2069
this.paymentMethods = response.data
2170
},
71+
72+
async fetchDefaultPaymentMethod(): Promise<void> {
73+
const response: any = await fetch(`${apiUrl}/stripe/default-payment-method`, {
74+
method: 'GET',
75+
headers: {
76+
'Content-Type': 'application/json',
77+
'Accept': 'application/json',
78+
},
79+
}).then(res => res.json())
80+
81+
this.defaultPaymentMethod = response
82+
},
2283
},
2384

2485
getters: {
2586
getPaymentMethods(state): any[] {
2687
return state.paymentMethods
2788
},
89+
getDefaultPaymentMethod(state): any[] {
90+
return state.defaultPaymentMethod
91+
},
2892
},
2993
})
3094

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script setup lang="ts">
2+
import { computed, defineProps } from 'vue'
3+
4+
const props = defineProps<{
5+
brand: string
6+
}>()
7+
8+
const logoSrc = computed(() => {
9+
switch (props.brand.toLowerCase()) {
10+
case 'visa':
11+
return '/images/logos/visa.png'
12+
case 'mastercard':
13+
case 'mc':
14+
return '/images/logos/mastercard.svg'
15+
case 'amex':
16+
case 'american express':
17+
return '/images/logos/amex.jpg'
18+
default:
19+
return '/images/logos/default-card.svg' // Path to a default card image if brand is unrecognized
20+
}
21+
})
22+
</script>
23+
24+
<template>
25+
<div class="w-24 h-24">
26+
<img :src="logoSrc" :alt="`${brand} Logo`" />
27+
</div>
28+
</template>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script setup lang="ts">
2+
const paymentStore = usePaymentStore()
3+
4+
const stripeLoading = ref(true)
5+
const showStripe = ref(false)
6+
7+
onMounted(async () => {
8+
await paymentStore.fetchUserPaymentMethods()
9+
})
10+
</script>
11+
12+
<template>
13+
<ul v-if="stripeLoading || !showStripe" role="list" class="grid grid-cols-1 mt-8 gap-6 lg:grid-cols-1 sm:grid-cols-1">
14+
<li v-for="(method, index) in paymentStore.getPaymentMethods" :key="index" class="col-span-1 border rounded-lg bg-white shadow divide-y divide-gray-200">
15+
<div class="w-full p-4">
16+
<div class="flex space-x-4">
17+
<div class="h-24 w-24">
18+
<img src="/images/logos/mastercard.svg" alt="Mastercard Logo" class="border">
19+
</div>
20+
<h2 class="text-xl text-gray-600">
21+
{{ method.card.brand }} •••• {{ method.card.last4 }} <br>
22+
<span class="text-xs text-gray-500 italic">Expires {{ method.card.exp_month }} / {{ method.card.exp_year }} </span>
23+
</h2>
24+
</div>
25+
26+
<div class="flex justify-end space-x-4">
27+
<button
28+
type="button"
29+
class="border rounded-md bg-white px-2 py-1 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
30+
>
31+
<svg class="h-4 w-4 text-gray-700" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
32+
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
33+
</svg>
34+
</button>
35+
<button
36+
type="button"
37+
class="border rounded-md bg-white px-2 py-1 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
38+
>
39+
<svg class="h-4 w-4 text-gray-700" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
40+
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
41+
</svg>
42+
</button>
43+
</div>
44+
</div>
45+
</li>
46+
</ul>
47+
</template>

resources/views/dashboard/components/billing/payment-method.vue

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<script setup lang="ts">
2+
import CardBrands from './card-brands.vue'
3+
24
import { useBillable } from '../../../../functions/billing/payments'
5+
36
const paymentStore = usePaymentStore()
47
58
const stripeLoading = ref(true)
@@ -20,8 +23,14 @@ function cancelPaymentForm() {
2023
stripeLoading.value = true
2124
}
2225
26+
function isEmpty(defaultPaymentMethod: any) {
27+
return !defaultPaymentMethod // Checks for null or undefined
28+
|| (typeof defaultPaymentMethod === 'object'
29+
&& Object.keys(defaultPaymentMethod).length === 0)
30+
}
31+
2332
onMounted(async () => {
24-
await paymentStore.fetchUserPaymentMethods()
33+
await paymentStore.fetchDefaultPaymentMethod()
2534
})
2635
</script>
2736

@@ -31,42 +40,41 @@ onMounted(async () => {
3140
Payment Info
3241
</h2>
3342

34-
<ul v-if="stripeLoading || !showStripe" role="list" class="grid grid-cols-1 mt-8 gap-6 lg:grid-cols-1 sm:grid-cols-1">
35-
<li v-for="(method, index) in paymentStore.getPaymentMethods" :key="index" class="col-span-1 border rounded-lg bg-white shadow divide-y divide-gray-200">
36-
<div class="w-full p-4">
37-
<div class="flex space-x-4">
38-
<div class="h-24 w-24">
39-
<img src="/images/logos/mastercard.svg" alt="Mastercard Logo" class="border">
40-
</div>
41-
<h2 class="text-xl text-gray-600">
42-
{{ method.card.brand }} •••• {{ method.card.last4 }} <br>
43-
<span class="text-xs text-gray-500 italic">Expires {{ method.card.exp_month }} / {{ method.card.exp_year }} </span>
44-
</h2>
43+
<div v-if="!isEmpty(paymentStore.getDefaultPaymentMethod)" class="col-span-1 border rounded-lg bg-white shadow divide-y divide-gray-200">
44+
<div class="w-full p-4">
45+
<div class="flex space-x-4">
46+
<CardBrands :brand="paymentStore.getDefaultPaymentMethod.card.brand" />
47+
<h2 class="text-xl text-gray-600">
48+
{{ paymentStore.getDefaultPaymentMethod.card.brand }} •••• {{ paymentStore.getDefaultPaymentMethod.card.last4 }}
49+
<br>
50+
<span class="text-xs text-gray-500 italic">Expires {{ paymentStore.getDefaultPaymentMethod.card.exp_month }} / {{ paymentStore.getDefaultPaymentMethod.card.exp_year }} </span>
51+
</h2>
52+
53+
<div>
54+
<span class="inline-flex items-center rounded-md bg-indigo-50 px-2 py-1 text-sm text-xs text-indigo-700 font-medium ring-1 ring-indigo-700/10 ring-inset">Default</span>
4555
</div>
56+
</div>
4657

47-
<div class="flex justify-end space-x-4">
48-
<button
49-
type="button"
50-
class="border rounded-md bg-white px-2 py-1 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
51-
@click="handleAddPaymentMethod()"
52-
>
53-
<svg class="h-4 w-4 text-gray-700" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
54-
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
55-
</svg>
56-
</button>
57-
<button
58-
type="button"
59-
class="border rounded-md bg-white px-2 py-1 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
60-
@click="handleAddPaymentMethod()"
61-
>
62-
<svg class="h-4 w-4 text-gray-700" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
63-
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
64-
</svg>
65-
</button>
66-
</div>
58+
<div class="flex justify-end space-x-4">
59+
<button
60+
type="button"
61+
class="border rounded-md bg-white px-2 py-1 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
62+
>
63+
<svg class="h-4 w-4 text-gray-700" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
64+
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
65+
</svg>
66+
</button>
67+
<button
68+
type="button"
69+
class="border rounded-md bg-white px-2 py-1 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
70+
>
71+
<svg class="h-4 w-4 text-gray-700" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
72+
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
73+
</svg>
74+
</button>
6775
</div>
68-
</li>
69-
</ul>
76+
</div>
77+
</div>
7078

7179
<div v-show="!stripeLoading || showStripe">
7280
<form id="payment-form">

routes/api.ts

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

28+
route.get('/stripe/default-payment-method', 'Actions/Payment/FetchDefaultPaymentMethodAction')
2829
route.get('/stripe/user-payment-methods', 'Actions/Payment/FetchPaymentMethodsAction')
2930
route.get('/stripe/create-setup-intent', 'Actions/Payment/CreateSetupIntentAction')
3031
route.post('/stripe/create-payment-intent', 'Actions/Payment/CreatePaymentIntentAction')

storage/framework/core/components/dropdown/src/components/Usage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async function handleCopyCode() {
4747

4848
The <code><b>DropdownButton</b></code> will automatically open/close the <code><b>DropdownItems</b></code> when clicked, and when the menu is open, the list of items receives focus and is automatically navigable via the keyboard.
4949
</p>
50-
<div class="code-block group relative">
50+
<div class="group code-block relative">
5151
<Highlight
5252
class-name="rounded-md text-xs"
5353
language="xml"

0 commit comments

Comments
 (0)