Skip to content

Commit 948b161

Browse files
committed
fix: order code and i18n context
1 parent dbb62f1 commit 948b161

File tree

13 files changed

+145
-29
lines changed

13 files changed

+145
-29
lines changed

app/api/payment.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { PaymentStatus } from '@base/server/db/schemas'
2+
13
export function useApiPayment() {
24
function checkout(type: 'payos' | 'vnpay' | 'sepay', productIdentifier: string) {
35
if (type !== 'payos' && type !== 'vnpay' && type !== 'sepay')
@@ -16,7 +18,20 @@ export function useApiPayment() {
1618
})
1719
}
1820

21+
function checkStatus(type: 'payos' | 'vnpay' | 'sepay', description: string) {
22+
return $api<{
23+
data: {
24+
status: PaymentStatus
25+
}
26+
}>(`api/payments/${type}/status`, {
27+
params: {
28+
description,
29+
},
30+
})
31+
}
32+
1933
return {
2034
checkout,
35+
checkStatus,
2136
}
2237
}

app/pages/checkout.vue

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { parseQuery } from 'ufo'
3+
import { PaymentStatus } from '@base/server/db/schemas'
34
45
definePageMeta({
56
middleware(to) {
@@ -29,9 +30,30 @@ const checkoutInfo = computed(() => {
2930
return query
3031
})
3132
32-
function handleCheckStatus() {
33-
//
34-
}
33+
const paymentApi = useApiPayment()
34+
35+
const { data, error, execute: handleCheckStatus } = useAsyncData(
36+
'checkoutInfo',
37+
() => paymentApi.checkStatus('sepay', String(checkoutInfo.value.des)),
38+
{ server: false, immediate: false },
39+
)
40+
41+
whenever(error, (err) => {
42+
notifyError({
43+
content: getErrorMessage(err),
44+
})
45+
})
46+
47+
whenever(data, (response) => {
48+
if (response?.data?.status === PaymentStatus.RESOLVED) {
49+
navigateTo({ name: 'app' })
50+
}
51+
else {
52+
notifyError({
53+
content: 'We have not received your payment yet. Please try again later, or contact support if the issue persists.',
54+
})
55+
}
56+
})
3557
</script>
3658

3759
<template>
@@ -61,12 +83,12 @@ function handleCheckStatus() {
6183
<span class="text-lg font-medium text-gray-500 dark:text-gray-400">{{ $t('Bank Name') }}</span>
6284
<span class="text-lg font-semibold text-gray-900 dark:text-white">{{ checkoutInfo.bank }}</span>
6385
</div>
64-
<UDivider />
86+
6587
<div class="flex justify-between items-center">
6688
<span class="text-lg font-medium text-gray-500 dark:text-gray-400">{{ $t('Amount') }}</span>
6789
<span class="text-xl font-bold text-primary-500 dark:text-primary-400">{{ checkoutInfo.amount }}</span>
6890
</div>
69-
<UDivider />
91+
7092
<div class="flex justify-between items-start">
7193
<span class="text-lg font-medium text-gray-500 dark:text-gray-400">{{ $t('Description') }}</span>
7294
<span class="text-lg text-gray-700 dark:text-gray-300 text-right">{{ checkoutInfo.des }}</span>
@@ -80,7 +102,7 @@ function handleCheckStatus() {
80102
<div>
81103
<UButton
82104
class="w-full font-semibold mt-4" size="xl"
83-
@click="handleCheckStatus"
105+
@click="handleCheckStatus()"
84106
>
85107
{{ $t('I have transfered the money! (Click here)') }}
86108
</UButton>

app/pages/pricing.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,15 @@ tryOnBeforeMount(async () => {
9494
const isProcessing = Boolean(pendingPaymentPrice.value)
9595
9696
if (isProcessing) {
97-
const { data: checkoutData } = await paymentApi.checkout('payos', pendingPaymentPrice.value)
97+
const { data: checkoutData } = await paymentApi.checkout('sepay', pendingPaymentPrice.value)
9898
pendingPaymentPrice.value = null
9999
100-
window.open(checkoutData.paymentUrl, '_blank')
100+
navigateTo({
101+
name: 'checkout',
102+
query: {
103+
qr: checkoutData.paymentUrl,
104+
},
105+
})
101106
}
102107
}
103108
})

app/utils/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const $api = $fetch.create({
1414
options.headers.set('Csrf-Token', authStore.crsfToken)
1515
},
1616
async onResponseError(error) {
17-
const { t } = useI18n()
17+
const { t } = useSafeI18n()
1818

1919
const isRequestFromExternalUrl = !String(error.response.url).startsWith(String(useRuntimeConfig().public.appBaseUrl))
2020
switch (error.response?.status) {

app/utils/i18n.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export function useSafeI18n() {
2+
const nuxtApp = useNuxtApp()
3+
4+
if (nuxtApp.$i18n) {
5+
return nuxtApp.$i18n
6+
}
7+
else {
8+
// mock useI18n
9+
return {
10+
t: (key: string) => key,
11+
locale: 'en',
12+
availableLocales: ['en'],
13+
setLocale: (locale: string) => {
14+
console.warn(`Setting locale to ${locale} is not supported in this mock implementation.`)
15+
},
16+
}
17+
}
18+
}

app/utils/notification.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ interface NotificationOptions {
22
content: string
33
}
44
export function notifyError(options: NotificationOptions) {
5-
const { t } = useI18n()
5+
const { t } = useSafeI18n()
66
const toast = useToast()
77

88
toast.add({
@@ -13,7 +13,7 @@ export function notifyError(options: NotificationOptions) {
1313
}
1414

1515
export function notifySuccess(options: NotificationOptions) {
16-
const { t } = useI18n()
16+
const { t } = useSafeI18n()
1717
const toast = useToast()
1818

1919
toast.add({
@@ -24,7 +24,7 @@ export function notifySuccess(options: NotificationOptions) {
2424
}
2525

2626
export function notifyWarning(options: NotificationOptions) {
27-
const { t } = useI18n()
27+
const { t } = useSafeI18n()
2828
const toast = useToast()
2929

3030
toast.add({
@@ -35,7 +35,7 @@ export function notifyWarning(options: NotificationOptions) {
3535
}
3636

3737
export function notifyInfo(options: NotificationOptions) {
38-
const { t } = useI18n()
38+
const { t } = useSafeI18n()
3939
const toast = useToast()
4040

4141
toast.add({

i18n/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,11 @@
225225
"Error": "Error",
226226
"Success": "Success",
227227
"Warning": "Warning",
228-
"Infor": "Infor"
228+
"Infor": "Infor",
229+
"Payment Summary": "Payment Summary",
230+
"Bank Name": "Bank Name",
231+
"Amount": "Amount",
232+
"Description": "Description",
233+
"Please verify the details before proceeding with the payment.": "Please verify the details before proceeding with the payment.",
234+
"I have transfered the money! (Click here)": "I have transfered the money! (Click here)"
229235
}

i18n/vi.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,11 @@
225225
"Error": "Lỗi",
226226
"Success": "Thành công",
227227
"Warning": "Cảnh báo",
228-
"Infor": "Thông tin"
228+
"Infor": "Thông tin",
229+
"Payment Summary": "Thông tin thanh toán",
230+
"Bank Name": "Ngân hàng",
231+
"Amount": "Số tiền",
232+
"Description": "Mô tả",
233+
"Please verify the details before proceeding with the payment.": "Vui lòng xác minh thông tin trước khi tiếp tục thanh toán.",
234+
"I have transfered the money! (Click here)": "Tôi đã chuyển tiền! (Nhấp vào đây)"
229235
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { z } from 'zod'
2+
3+
import { PaymentStatus } from '@base/server/db/schemas'
4+
5+
export default defineEventHandler(async (event) => {
6+
try {
7+
await defineEventOptions(event, { auth: true })
8+
9+
// description must be in format: uuid.uuid
10+
11+
const { description } = await getValidatedQuery(
12+
event,
13+
query => z.object({
14+
description: z.string().min(1, 'Payment description must be in the correct format!'),
15+
})
16+
.refine((query) => {
17+
const [part1, part2] = query.description.split('.')
18+
19+
return isUUID(part1) && isUUID(part2)
20+
}, { message: 'Payment description must be in the correct format!' })
21+
.parse(query),
22+
)
23+
24+
const [orderCode] = description.split('.')
25+
26+
const { getProviderTransactionByOrderCode } = usePayment()
27+
28+
const paymentTransactionOfProvider = await getProviderTransactionByOrderCode(String(orderCode))
29+
30+
return {
31+
data: {
32+
status: paymentTransactionOfProvider?.provider_transaction_status || PaymentStatus.PENDING,
33+
},
34+
}
35+
}
36+
catch (error: any) {
37+
logger.error('[Payment API] Error creating SePay checkout URL:', error)
38+
39+
throw parseError(error)
40+
}
41+
})

server/composables/usePayment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ export function usePayment() {
3434
async function createProviderTransaction(
3535
paymentId: string,
3636
userId: string,
37-
orderCode: number,
37+
orderCode: string,
3838
provider: string,
3939
productType: string,
4040
productInfo: Record<string, any>,
4141
): Promise<PaymentProviderTransaction> {
4242
return (
4343
await db.insert(paymentProviderTransactionTable).values({
4444
provider,
45-
provider_transaction_id: String(orderCode),
45+
provider_transaction_id: orderCode,
4646
provider_transaction_status: PaymentStatus.PENDING,
4747
provider_transaction_info: `${productType}:${productInfo.amount}`,
4848
payment_id: paymentId,

0 commit comments

Comments
 (0)