Skip to content

Commit dbb62f1

Browse files
committed
feat: checkout page
1 parent 596890d commit dbb62f1

File tree

6 files changed

+122
-6
lines changed

6 files changed

+122
-6
lines changed

app/api/payment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export function useApiPayment() {
2-
function checkout(type: 'payos' | 'vnpay', productIdentifier: string) {
3-
if (type !== 'payos' && type !== 'vnpay')
2+
function checkout(type: 'payos' | 'vnpay' | 'sepay', productIdentifier: string) {
3+
if (type !== 'payos' && type !== 'vnpay' && type !== 'sepay')
44
throw new Error('Invalid payment provider')
55

66
return $api<{

app/components/Billing/BillingSubscriptionCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const billingCycleProgress = computed(() => {
6565

6666
<div class="mt-2">
6767
<strong>
68-
{{ new Intl.NumberFormat('en-US', { style: 'currency', currency: (subscription?.currency || 'usd').toUpperCase() }).format((subscription?.items.data[0]?.price.unit_amount || 0) / 100) }} {{ $t('Per Month') }}
68+
{{ new Intl.NumberFormat('en-US', { style: 'currency', currency: (subscription?.currency || 'USD').toUpperCase() }).format((subscription?.items.data[0]?.price.unit_amount || 0) / 100) }} {{ $t('Per Month') }}
6969
</strong>
7070

7171
<UBadge v-if="subscription?.metadata.hightlight" :label="$t('Popular')" />

app/pages/checkout.vue

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<script setup lang="ts">
2+
import { parseQuery } from 'ufo'
3+
4+
definePageMeta({
5+
middleware(to) {
6+
if (!to.query.qr) {
7+
return navigateTo({ name: 'pricing' })
8+
}
9+
},
10+
})
11+
12+
const { data: page } = await useAsyncData('pricing', () => queryCollection('pricing').first())
13+
14+
useSeoMeta({
15+
title: page.value?.title,
16+
ogTitle: page.value?.title,
17+
description: page.value?.description,
18+
ogDescription: page.value?.description,
19+
})
20+
21+
defineOgImageComponent('Saas')
22+
23+
const route = useRoute()
24+
25+
const checkoutQr = computed(() => String(route.query.qr))
26+
const checkoutInfo = computed(() => {
27+
const query = parseQuery(checkoutQr.value)
28+
29+
return query
30+
})
31+
32+
function handleCheckStatus() {
33+
//
34+
}
35+
</script>
36+
37+
<template>
38+
<div v-if="page">
39+
<UPageSection
40+
:title="page.topup.title"
41+
:description="page.topup.description"
42+
>
43+
<div class="grid md:grid-cols-2 gap-4 items-start">
44+
<div class="flex justify-center">
45+
<img
46+
:src="String($route.query.qr)"
47+
alt="QR Code for payment"
48+
class="rounded-lg shadow-xl max-w-sm w-full"
49+
>
50+
</div>
51+
52+
<UCard class="w-full">
53+
<template #header>
54+
<h3 class="text-xl font-semibold leading-6 text-gray-900 dark:text-white">
55+
{{ $t('Payment Summary') }}
56+
</h3>
57+
</template>
58+
59+
<div class="space-y-4">
60+
<div class="flex justify-between items-center">
61+
<span class="text-lg font-medium text-gray-500 dark:text-gray-400">{{ $t('Bank Name') }}</span>
62+
<span class="text-lg font-semibold text-gray-900 dark:text-white">{{ checkoutInfo.bank }}</span>
63+
</div>
64+
<UDivider />
65+
<div class="flex justify-between items-center">
66+
<span class="text-lg font-medium text-gray-500 dark:text-gray-400">{{ $t('Amount') }}</span>
67+
<span class="text-xl font-bold text-primary-500 dark:text-primary-400">{{ checkoutInfo.amount }}</span>
68+
</div>
69+
<UDivider />
70+
<div class="flex justify-between items-start">
71+
<span class="text-lg font-medium text-gray-500 dark:text-gray-400">{{ $t('Description') }}</span>
72+
<span class="text-lg text-gray-700 dark:text-gray-300 text-right">{{ checkoutInfo.des }}</span>
73+
</div>
74+
</div>
75+
76+
<template #footer>
77+
<p class="text-lg text-gray-500 dark:text-gray-400">
78+
{{ $t('Please verify the details before proceeding with the payment.') }}
79+
</p>
80+
<div>
81+
<UButton
82+
class="w-full font-semibold mt-4" size="xl"
83+
@click="handleCheckStatus"
84+
>
85+
{{ $t('I have transfered the money! (Click here)') }}
86+
</UButton>
87+
</div>
88+
</template>
89+
</UCard>
90+
</div>
91+
</UPageSection>
92+
93+
<UPageSection
94+
:title="page.faq.title"
95+
:description="page.faq.description"
96+
>
97+
<UPageAccordion
98+
multiple
99+
class="max-w-4xl mx-auto"
100+
:items="page.faq.items"
101+
/>
102+
</UPageSection>
103+
</div>
104+
</template>

app/pages/pricing.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,17 @@ async function handleCheckout() {
7373
await navigateTo({ path: '/sign-in' }, { external: true })
7474
}
7575
76-
const { data: checkoutData } = await paymentApi.checkout('payos', productIdentifier)
76+
// const { data: checkoutData } = await paymentApi.checkout('payos', productIdentifier)
7777
78-
window.open(checkoutData.paymentUrl, '_blank')
78+
// window.open(checkoutData.paymentUrl, '_blank')
79+
const { data: checkoutData } = await paymentApi.checkout('sepay', productIdentifier)
80+
81+
navigateTo({
82+
name: 'checkout',
83+
query: {
84+
qr: checkoutData.paymentUrl,
85+
},
86+
})
7987
}
8088
8189
tryOnBeforeMount(async () => {

app/utils/currency.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export function formatPrice(price: number, currency: string) {
2+
if (currency !== 'USD' && currency !== 'VND') {
3+
return price
4+
}
5+
26
return new Intl.NumberFormat('vi-VN', { style: 'currency', currency }).format(price)
37
}

server/utils/payment/vn/sepay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { paymentProviderTransactionTable } from '@base/server/db/schemas'
22
import { withQuery } from 'ufo'
33

44
interface SePayCheckoutProps {
5-
orderCode: string
5+
orderCode: number
66
amount: number
77
paymentProviderTransaction: typeof paymentProviderTransactionTable.$inferSelect
88
}

0 commit comments

Comments
 (0)