update payment
This commit is contained in:
68
package-lock.json
generated
68
package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"@graphql-codegen/typescript-resolvers": "^4.2.1",
|
||||
"@nestjs-modules/mailer": "^2.0.2",
|
||||
"@nestjs/apollo": "^12.2.0",
|
||||
"@nestjs/axios": "^3.1.1",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.3",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
@@ -24,6 +25,7 @@
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/swagger": "^7.4.2",
|
||||
"@payos/node": "^1.0.10",
|
||||
"@pothos/core": "^4.3.0",
|
||||
"@pothos/plugin-add-graphql": "^4.1.0",
|
||||
"@pothos/plugin-authz": "^3.5.10",
|
||||
@@ -38,6 +40,7 @@
|
||||
"@smatch-corp/nestjs-pothos": "^0.3.0",
|
||||
"@smatch-corp/nestjs-pothos-apollo-driver": "^0.1.0",
|
||||
"apollo-server-express": "^3.13.0",
|
||||
"axios": "^1.7.7",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
@@ -4284,6 +4287,17 @@
|
||||
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@nestjs/axios": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.1.1.tgz",
|
||||
"integrity": "sha512-ySoxrzqX80P1q6LKLKGcgyBd2utg4gbC+4FsJNpXYvILorMlxss/ECNogD9EXLCE4JS5exVFD5ez0nK5hXcNTQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||
"axios": "^1.3.1",
|
||||
"rxjs": "^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/cli": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.5.tgz",
|
||||
@@ -4928,6 +4942,16 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@payos/node": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@payos/node/-/node-1.0.10.tgz",
|
||||
"integrity": "sha512-dY+WHd6pLa558a1G8yv6oKfVe5QLTNyYnQBaSQtwvMAm/p0faKAnfXt04LNIwO9/4buas4ES+sDxc1bfX/mVbQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0",
|
||||
"crypto": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -7163,6 +7187,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
@@ -8388,6 +8423,13 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
|
||||
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
|
||||
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
@@ -9816,6 +9858,26 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
@@ -14947,6 +15009,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pug": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@graphql-codegen/typescript-resolvers": "^4.2.1",
|
||||
"@nestjs-modules/mailer": "^2.0.2",
|
||||
"@nestjs/apollo": "^12.2.0",
|
||||
"@nestjs/axios": "^3.1.1",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.3",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
@@ -46,6 +47,7 @@
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/swagger": "^7.4.2",
|
||||
"@payos/node": "^1.0.10",
|
||||
"@pothos/core": "^4.3.0",
|
||||
"@pothos/plugin-add-graphql": "^4.1.0",
|
||||
"@pothos/plugin-authz": "^3.5.10",
|
||||
@@ -60,6 +62,7 @@
|
||||
"@smatch-corp/nestjs-pothos": "^0.3.0",
|
||||
"@smatch-corp/nestjs-pothos-apollo-driver": "^0.1.0",
|
||||
"apollo-server-express": "^3.13.0",
|
||||
"axios": "^1.7.7",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
|
||||
@@ -152,7 +152,6 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
|
||||
this.queryType({})
|
||||
this.mutationType({})
|
||||
this.subscriptionType({})
|
||||
|
||||
this.globalConnectionField('totalCount', (t) =>
|
||||
t.int({
|
||||
nullable: true,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { OrderSchema } from './order.schema'
|
||||
|
||||
import { PayosModule } from 'src/Payos/payos.module'
|
||||
@Module({
|
||||
imports: [PayosModule],
|
||||
providers: [OrderSchema],
|
||||
exports: [OrderSchema],
|
||||
})
|
||||
|
||||
@@ -8,11 +8,14 @@ import {
|
||||
import { Builder } from '../Graphql/graphql.builder'
|
||||
import { PrismaService } from '../Prisma/prisma.service'
|
||||
import { OrderStatus } from '@prisma/client'
|
||||
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
|
||||
import { PayosService } from 'src/Payos/payos.service'
|
||||
@Injectable()
|
||||
export class OrderSchema extends PothosSchema {
|
||||
constructor(
|
||||
@Inject(SchemaBuilderToken) private readonly builder: Builder,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly payosService: PayosService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -121,11 +124,24 @@ export class OrderSchema extends PothosSchema {
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
resolve: async (query, _root, args, _ctx, _info) => {
|
||||
resolve: async (query, _root, args, ctx, _info) => {
|
||||
return this.prisma.$transaction(async (prisma) => {
|
||||
if (ctx.isSubscription) {
|
||||
throw new Error('Subscription is not allowed')
|
||||
}
|
||||
if (!args.data.service.connect?.id) {
|
||||
throw new Error('Service not found')
|
||||
}
|
||||
const order = await prisma.order.create({
|
||||
...query,
|
||||
data: args.data,
|
||||
data: {
|
||||
status: OrderStatus.PENDING,
|
||||
total:
|
||||
(args.data.service.connect?.price as number | undefined) ?? 0,
|
||||
userId: ctx.http.me.id,
|
||||
serviceId: args.data.service.connect.id,
|
||||
scheduleId: args.data.scheduleId,
|
||||
},
|
||||
})
|
||||
// check if service is valid
|
||||
if (!args.data.service.connect) {
|
||||
@@ -135,15 +151,32 @@ export class OrderSchema extends PothosSchema {
|
||||
if (args.data.service.connect.price === 0) {
|
||||
return order
|
||||
}
|
||||
// generate payment code by prefix 'EPESS' + 6 hex digits
|
||||
const paymentCode = 'EPESS' + Math.random().toString(16).slice(2, 8)
|
||||
// random integer
|
||||
const paymentCode = Math.floor(Math.random() * 1000000)
|
||||
// create payment
|
||||
await prisma.payment.create({
|
||||
const payment = await prisma.payment.create({
|
||||
data: {
|
||||
orderId: order.id,
|
||||
amount: args.data.service.connect.price as number,
|
||||
paymentCode: paymentCode,
|
||||
expiredAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
|
||||
paymentCode: paymentCode.toString(),
|
||||
expiredAt: DateTimeUtils.now().plus({ minutes: 15 }).toJSDate(),
|
||||
},
|
||||
})
|
||||
// generate payment url
|
||||
const paymentData = await this.payosService.createPayment({
|
||||
orderCode: paymentCode,
|
||||
amount: args.data.service.connect.price as number,
|
||||
description: args.data.service.connect.name as string,
|
||||
buyerName: ctx.http.me.name as string,
|
||||
buyerEmail: ctx.http.me.email as string,
|
||||
returnUrl: `${process.env.PAYOS_WEBHOOK_URL}/return`,
|
||||
cancelUrl: `${process.env.PAYOS_WEBHOOK_URL}/cancel`,
|
||||
})
|
||||
// update payment url
|
||||
await prisma.payment.update({
|
||||
where: { id: payment.id },
|
||||
data: {
|
||||
paymentCode: paymentData.paymentLinkId,
|
||||
},
|
||||
})
|
||||
return order
|
||||
|
||||
@@ -32,4 +32,18 @@ export class PayosController {
|
||||
async ping() {
|
||||
return this.payosService.ping()
|
||||
}
|
||||
|
||||
// test create payment url
|
||||
@Post('create-payment-url')
|
||||
@ApiOperation({ summary: 'Test create payment url' })
|
||||
async createPaymentURL(@Body() body: any) {
|
||||
return this.payosService.createPaymentURL(body)
|
||||
}
|
||||
|
||||
// get payment status
|
||||
@Get('get-payment-status/:orderId')
|
||||
@ApiOperation({ summary: 'Get payment status' })
|
||||
async getPaymentStatus(@Param('orderId') orderId: string | number) {
|
||||
return this.payosService.getPaymentStatus(orderId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PayosController } from './payos.controller'
|
||||
import { PayosService } from './payos.service'
|
||||
import { HttpModule } from '@nestjs/axios'
|
||||
import PayOS from '@payos/node'
|
||||
|
||||
@Module({
|
||||
providers: [PayosService],
|
||||
imports: [HttpModule],
|
||||
providers: [
|
||||
PayosService,
|
||||
{
|
||||
provide: 'PayOS',
|
||||
useFactory: () => {
|
||||
return new PayOS(
|
||||
process.env.PAYOS_CLIENT_ID ?? '',
|
||||
process.env.PAYOS_API_KEY ?? '',
|
||||
process.env.PAYOS_CHECKSUM_KEY ?? '',
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
controllers: [PayosController],
|
||||
exports: [PayosService],
|
||||
})
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common'
|
||||
|
||||
import { PrismaService } from '../Prisma/prisma.service'
|
||||
|
||||
import PayOS from '@payos/node'
|
||||
import type {
|
||||
CheckoutRequestType,
|
||||
CheckoutResponseDataType,
|
||||
} from '@payos/node/lib/type'
|
||||
export type CreatePaymentBody = CheckoutRequestType
|
||||
export type CreatePaymentResponse = CheckoutResponseDataType
|
||||
@Injectable()
|
||||
export class PayosService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject('PayOS') private readonly payos: PayOS,
|
||||
) {}
|
||||
|
||||
async ping() {
|
||||
return 'pong'
|
||||
@@ -16,7 +25,15 @@ export class PayosService {
|
||||
}
|
||||
|
||||
async createPaymentURL(body: any) {
|
||||
return body
|
||||
return await this.payos.createPaymentLink(body)
|
||||
}
|
||||
|
||||
async createPayment(body: CreatePaymentBody): Promise<CreatePaymentResponse> {
|
||||
return await this.payos.createPaymentLink(body)
|
||||
}
|
||||
|
||||
async getPaymentStatus(orderId: string | number) {
|
||||
return await this.payos.getPaymentLinkInformation(orderId)
|
||||
}
|
||||
|
||||
async cancelPaymentURL(body: any) {
|
||||
|
||||
@@ -72,6 +72,20 @@ export class ScheduleSchema extends PothosSchema {
|
||||
})
|
||||
}
|
||||
|
||||
@PothosRef()
|
||||
scheduleConnection() {
|
||||
return this.builder.simpleObject('ScheduleConnection', {
|
||||
fields: (t) => ({
|
||||
totalCount: t.int({
|
||||
nullable: true,
|
||||
}),
|
||||
schedules: t.field({
|
||||
type: [this.schedule()],
|
||||
}),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@PothosRef()
|
||||
scheduleSlot() {
|
||||
return this.builder.simpleObject('ScheduleSlot', {
|
||||
@@ -326,6 +340,28 @@ d72a864e-2f41-45ab-9c9b-bf0512a31883,e9be51fd-2382-4e43-9988-74e76fde4b56,2024-1
|
||||
})
|
||||
},
|
||||
}),
|
||||
|
||||
updateScheduleStatus: t.prismaField({
|
||||
type: this.schedule(),
|
||||
description: 'Update a schedule status.',
|
||||
args: {
|
||||
scheduleId: t.arg({
|
||||
type: 'String',
|
||||
required: true,
|
||||
}),
|
||||
status: t.arg({
|
||||
type: ScheduleStatus,
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
resolve: async (query, _root, args, _ctx, _info) => {
|
||||
return await this.prisma.schedule.update({
|
||||
...query,
|
||||
where: { id: args.scheduleId },
|
||||
data: { status: args.status },
|
||||
})
|
||||
},
|
||||
}),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,14 @@ export type TimeType = {
|
||||
|
||||
@Injectable()
|
||||
export class DateTimeUtils {
|
||||
static nowAsJSDate(): Date {
|
||||
return DateTime.now().toJSDate()
|
||||
}
|
||||
|
||||
static now(): DateTime {
|
||||
return DateTime.now()
|
||||
}
|
||||
|
||||
static getOverlapRange(
|
||||
startA: DateTime,
|
||||
endA: DateTime,
|
||||
|
||||
Reference in New Issue
Block a user