Skip to content

Commit eb344ab

Browse files
committed
feat: finalize sepay
1 parent 948b161 commit eb344ab

File tree

7 files changed

+51
-36
lines changed

7 files changed

+51
-36
lines changed

app/utils/i18n.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/**
2+
* A safe useI18n that have mocked fallbacks in case the app context is not available.
3+
*
4+
* Do not use this widely in the project, only in some plugins, auto-imports that potentially do not
5+
* have the app context like in notifications, $api, etc.
6+
*/
17
export function useSafeI18n() {
28
const nuxtApp = useNuxtApp()
39

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@thecodeorigin/nuxt",
33
"type": "module",
4-
"version": "2.5.6",
4+
"version": "2.6.0",
55
"publishConfig": {
66
"registry": "https://registry.npmjs.org",
77
"access": "public"
@@ -97,6 +97,7 @@
9797
"firebase-admin": "^13.2.0",
9898
"lodash-es": "^4.17.21",
9999
"mongodb": "^6.14.2",
100+
"nanoid": "^5.1.5",
100101
"nodemailer": "^6.10.0",
101102
"nuxt": "^3.16.0",
102103
"nuxt-gtag": "^3.0.2",

pnpm-lock.yaml

Lines changed: 19 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/api/payments/sepay/status.get.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,21 @@ export default defineEventHandler(async (event) => {
66
try {
77
await defineEventOptions(event, { auth: true })
88

9-
// description must be in format: uuid.uuid
10-
119
const { description } = await getValidatedQuery(
1210
event,
1311
query => z.object({
1412
description: z.string().min(1, 'Payment description must be in the correct format!'),
1513
})
1614
.refine((query) => {
17-
const [part1, part2] = query.description.split('.')
15+
const orderCode = query.description.slice(2)
1816

19-
return isUUID(part1) && isUUID(part2)
17+
return orderCode.length === 16
2018
}, { message: 'Payment description must be in the correct format!' })
2119
.parse(query),
2220
)
2321

24-
const [orderCode] = description.split('.')
22+
// remove the first 2 letters
23+
const orderCode = description.slice(2)
2524

2625
const { getProviderTransactionByOrderCode } = usePayment()
2726

server/api/payments/sepay/webhook.post.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default defineEventHandler(async (event) => {
3232
transactionDate: z.string(),
3333
transferAmount: z.number(),
3434
transferType: z.string(),
35-
}).partial().parse(payload),
35+
}).parse(payload),
3636
)
3737

3838
if (process.env.SEPAY_WEBHOOK_SIGNING_KEY !== getHeader(event, 'Authorization')?.match(/Apikey (.*)/)?.[1]) {
@@ -48,17 +48,18 @@ export default defineEventHandler(async (event) => {
4848
// SePay Webhook always success (if not, it will not call this endpoint anyway)
4949

5050
const transactionStatus = PaymentStatus.RESOLVED
51+
const orderCode = body.code.slice(2) || '' // Remove the first 2 characters (SP)
5152

5253
const { updatePaymentStatus, updateProviderTransactionStatus, getProviderTransactionByOrderCode } = usePayment()
5354

54-
const paymentTransactionOfProvider = await getProviderTransactionByOrderCode(String(body.code))
55+
const paymentTransactionOfProvider = await getProviderTransactionByOrderCode(String(orderCode))
5556

5657
if (!paymentTransactionOfProvider?.payment.order.package) {
57-
logger.warn(`[SePay Webhook] Transaction not found or invalid: code=${body.code}`)
58+
logger.warn(`[SePay Webhook] Transaction not found or invalid: code=${orderCode}`)
5859
return { success: true }
5960
}
6061

61-
logger.log(`[SePay Webhook] Processing transaction: code=${body.code}, status=${transactionStatus}`)
62+
logger.log(`[SePay Webhook] Processing transaction: code=${orderCode}, status=${transactionStatus}`)
6263

6364
const priceDiscount = Number(paymentTransactionOfProvider.payment.order.package.price_discount)
6465
const price = Number(paymentTransactionOfProvider.payment.order.package.price)
@@ -84,7 +85,7 @@ export default defineEventHandler(async (event) => {
8485
logger.log(`[SePay Webhook] Credits added successfully: userId=${userId}, amount=${creditAmount}`)
8586

8687
if (!paymentTransactionOfProvider?.payment.order.package) {
87-
logger.error(`[SePay Webhook] No product found for transaction: ${body.code}`)
88+
logger.error(`[SePay Webhook] No product found for transaction: ${orderCode}`)
8889
throw createError({
8990
statusCode: 400,
9091
message: 'No product found for this transaction!',

server/utils/payment/vn/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { User } from '@base/server/types/models'
2-
import { v4 as uuid } from 'uuid'
3-
import { createPayOSCheckout } from './payos'
2+
import { customAlphabet, nanoid } from 'nanoid'
43

54
export * from './payos'
65

@@ -53,9 +52,10 @@ export async function createPaymentCheckout(
5352
Number(productInfo.price_discount || productInfo.price),
5453
)
5554

56-
const orderCode = uuid()
55+
// exclude underscore _
56+
const orderCode = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 16)()
5757

58-
const paymentProviderTransaction = await createProviderTransaction(
58+
await createProviderTransaction(
5959
userPayment.id,
6060
payload.user.id,
6161
orderCode,
@@ -77,7 +77,6 @@ export async function createPaymentCheckout(
7777
return await createSePayCheckout({
7878
orderCode,
7979
amount: userPayment.amount,
80-
paymentProviderTransaction,
8180
})
8281

8382
default:

server/utils/payment/vn/sepay.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
1-
import type { paymentProviderTransactionTable } from '@base/server/db/schemas'
21
import { withQuery } from 'ufo'
32

43
interface SePayCheckoutProps {
54
orderCode: string
65
amount: number
7-
paymentProviderTransaction: typeof paymentProviderTransactionTable.$inferSelect
86
}
97

108
export async function createSePayCheckout({
119
orderCode,
1210
amount,
13-
paymentProviderTransaction,
1411
}: SePayCheckoutProps) {
12+
const prefix = process.env.SEPAY_TRANSACTION_PREFIX || 'SP'
13+
14+
if (prefix.length !== 2) {
15+
throw createError({
16+
statusCode: 400,
17+
statusMessage: 'Transaction prefix must be exactly 2 characters long.',
18+
})
19+
}
20+
1521
return withQuery('https://qr.sepay.vn/img', {
1622
acc: process.env.SEPAY_BANK_NUMBER,
1723
bank: process.env.SEPAY_BANK_NAME,
1824
amount,
19-
des: [orderCode, paymentProviderTransaction.provider_transaction_info].join('.'),
25+
des: [prefix, orderCode].join(''),
2026
template: 'compact',
2127
download: false,
2228
})

0 commit comments

Comments
 (0)