Skip to content

Commit

Permalink
feature/payment: integrate PAYOS payment (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
MinhhTien authored May 5, 2024
1 parent f15b630 commit ac8b980
Show file tree
Hide file tree
Showing 18 changed files with 468 additions and 255 deletions.
5 changes: 5 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ ZALOPAY_APP_ID=
ZALOPAY_KEY1=
ZALOPAY_KEY2=
ZALOPAY_ENDPOINT=

#PAYOS
PAYOS_CLIENT_ID=
PAYOS_API_KEY=
PAYOS_CHECKSUM_KEY=
24 changes: 20 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "efurniture-api",
"version": "0.0.1",
"description": "efurniture API",
"name": "furnique-api",
"version": "1.0.0",
"description": "furnique API",
"author": "Deco-Team",
"license": "UNLICENSED",
"scripts": {
Expand Down Expand Up @@ -29,6 +29,7 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.2.0",
"@payos/node": "^1.0.6",
"@types/moment": "^2.13.0",
"axios": "^1.6.5",
"bcrypt": "^5.1.1",
Expand Down
2 changes: 1 addition & 1 deletion src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export class AppService {
constructor(private readonly i18nService: I18nService) {}

getHello(): string {
return 'Welcome to eFurniture!'
return 'Welcome to Furnique!'
}

getI18nText(): string {
Expand Down
5 changes: 5 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export default () => ({
key1: process.env.ZALOPAY_KEY1,
key2: process.env.ZALOPAY_KEY2,
endpoint: process.env.ZALOPAY_ENDPOINT
},
payos: {
clientId: process.env.PAYOS_CLIENT_ID,
apiKey: process.env.PAYOS_API_KEY,
checksumKey: process.env.PAYOS_CHECKSUM_KEY,
}
},
JWT_ACCESS_SECRET: process.env.JWT_ACCESS_SECRET || 'accessSecret',
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async function bootstrap() {
// add api-docs
if (process.env.NODE_ENV === 'development') {
const config = new DocumentBuilder()
.setTitle('eFurniture Swagger')
.setTitle('Furnique Swagger')
.setDescription('Nestjs API documentation')
.setVersion(process.env.npm_package_version || '1.0.0')
.addBearerAuth()
Expand Down Expand Up @@ -58,6 +58,6 @@ async function bootstrap() {

const port = process.env.PORT || 5000
await app.listen(port)
logger.debug(`🚕 ==>> eFurniture Server is running on port ${port} <<== 🚖`)
logger.debug(`🚕 ==>> Furnique Server is running on port ${port} <<== 🚖`)
}
bootstrap()
4 changes: 2 additions & 2 deletions src/order/controllers/customer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { OrderService } from '@order/services/order.service'
import { OrderHistoryDto } from '@order/schemas/order.schema'
import { Pagination, PaginationParams } from '@common/decorators/pagination.decorator'
import { DataResponse } from '@common/contracts/openapi-builder'
import { CreateMomoPaymentResponseDto } from '@payment/dto/momo-payment.dto'
import { CreatePayOSPaymentResponseDto } from '@payment/dto/payos-payment.dto'

@ApiTags('Order - Customer')
@ApiBearerAuth()
Expand All @@ -26,7 +26,7 @@ export class OrderCustomerController {
@ApiOperation({
summary: 'Create new order(orderStatus: PENDING, transactionStatus: DRAFT)'
})
@ApiOkResponse({ type: CreateMomoPaymentResponseDto })
@ApiOkResponse({ type: CreatePayOSPaymentResponseDto })
@ApiBadRequestResponse({ type: ErrorResponse })
async createOrder(@Req() req, @Body() createOrderDto: CreateOrderDto) {
const { _id, role } = _.get(req, 'user')
Expand Down
177 changes: 96 additions & 81 deletions src/order/services/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,10 @@ import { ProductRepository } from '@product/repositories/product.repository'
import { PaymentRepository } from '@payment/repositories/payment.repository'
import { PaymentMethod } from '@payment/contracts/constant'
import { PaymentService } from '@payment/services/payment.service'
import {
CreateMomoPaymentDto,
CreateMomoPaymentResponse,
QueryMomoPaymentDto,
RefundMomoPaymentDto
} from '@payment/dto/momo-payment.dto'
import { CreateMomoPaymentResponse, QueryMomoPaymentDto } from '@payment/dto/momo-payment.dto'
import { ConfigService } from '@nestjs/config'
import { MailerService } from '@nestjs-modules/mailer'
import { CheckoutRequestType, CheckoutResponseDataType, PaymentLinkDataType } from '@payos/node/lib/type'

@Injectable()
export class OrderService {
Expand Down Expand Up @@ -132,37 +128,55 @@ export class OrderService {
})

// 4. Process transaction
let createMomoPaymentResponse: CreateMomoPaymentResponse
const orderId = `FUR${new Date().getTime()}${Math.floor(Math.random() * 100)}`
let paymentResponseData: CreateMomoPaymentResponse | PaymentLinkDataType
let checkoutData: CreateMomoPaymentResponse | CheckoutResponseDataType
const MAX_VALUE = 9_007_199_254_740_991
const MIM_VALUE = 1_000_000_000_000_000
const orderCode = Math.floor(MIM_VALUE + Math.random() * (MAX_VALUE - MIM_VALUE))
createOrderDto['paymentMethod'] = PaymentMethod.PAY_OS
switch (createOrderDto.paymentMethod) {
case PaymentMethod.ZALO_PAY:
// case PaymentMethod.MOMO:
// this.paymentService.setStrategy(PaymentMethod.MOMO)
// const createMomoPaymentDto: CreateMomoPaymentDto = {
// partnerName: 'FURNIQUE',
// orderInfo: `Furnique - Thanh toán đơn hàng #${orderCode}`,
// redirectUrl: `${this.configService.get('WEB_URL')}/customer/orders`,
// ipnUrl: `${this.configService.get('SERVER_URL')}/payment/webhook/momo`,
// requestType: 'payWithMethod',
// amount: totalAmount,
// orderId: orderCode.toString(),
// requestId: orderCode.toString(),
// extraData: '',
// autoCapture: true,
// lang: 'vi',
// orderExpireTime: 15
// }
// paymentResponseData = checkoutData = await this.paymentService.createTransaction(createMomoPaymentDto)
// break
// case PaymentMethod.ZALO_PAY:
// implement later
case PaymentMethod.MOMO:
case PaymentMethod.PAY_OS:
default:
this.paymentService.setStrategy(this.paymentService.momoPaymentStrategy)
const createMomoPaymentDto: CreateMomoPaymentDto = {
partnerName: 'FURNIQUE',
orderInfo: `Furnique - Thanh toán đơn hàng #${orderId}`,
redirectUrl: `${this.configService.get('WEB_URL')}/customer/orders`,
ipnUrl: `${this.configService.get('SERVER_URL')}/payment/webhook`,
requestType: 'payWithMethod',
this.paymentService.setStrategy(PaymentMethod.PAY_OS)
const checkoutRequestType: CheckoutRequestType = {
orderCode: orderCode,
amount: totalAmount,
orderId,
requestId: orderId,
extraData: '',
autoCapture: true,
lang: 'vi',
orderExpireTime: 15
description: `FUR-Thanh toán đơn hàng`,
// TODO: Update link below
cancelUrl: `${this.configService.get('WEB_URL')}/customer/orders`,
returnUrl: `${this.configService.get('WEB_URL')}/customer/orders`
}
createMomoPaymentResponse = await this.paymentService.createTransaction(createMomoPaymentDto)
checkoutData = await this.paymentService.createTransaction(checkoutRequestType)
paymentResponseData = await this.paymentService.getTransaction(checkoutData['orderCode'])
break
}

// 5. Create payment
const payment = await this.paymentRepository.create(
{
transactionStatus: TransactionStatus.DRAFT,
transaction: createMomoPaymentResponse,
transaction: paymentResponseData,
transactionHistory: [paymentResponseData],
paymentMethod: createOrderDto.paymentMethod,
amount: totalAmount
},
Expand All @@ -175,7 +189,7 @@ export class OrderService {
await this.orderRepository.create(
{
...createOrderDto,
orderId,
orderId: orderCode.toString(),
items: orderItems,
totalAmount,
payment
Expand All @@ -185,7 +199,7 @@ export class OrderService {
}
)
await session.commitTransaction()
return createMomoPaymentResponse
return checkoutData
} catch (error) {
await session.abortTransaction()
console.error(error)
Expand Down Expand Up @@ -296,65 +310,66 @@ export class OrderService {
})
await this.productRepository.model.bulkWrite(operations)

// 3. Refund payment via MOMO
this.logger.log(`3. Refund payment via MOMO::`)
const refundOrderId = `FUR${new Date().getTime()}${Math.floor(Math.random() * 100)}`
this.paymentService.setStrategy(this.paymentService.momoPaymentStrategy)
const refundMomoPaymentDto: RefundMomoPaymentDto = {
orderId: refundOrderId,
requestId: refundOrderId,
amount: order.payment?.amount,
transId: order.payment?.transaction['transId'],
lang: 'vi',
description: `Furnique - Hoàn tiền đơn hàng #${orderId}`
}
const refundedTransaction = await this.paymentService.refundTransaction(refundMomoPaymentDto)
this.logger.log(JSON.stringify(refundedTransaction))
// TODO: check if PaymentMethod === MOMO
// // 3. Refund payment via MOMO
// this.logger.log(`3. Refund payment via MOMO::`)
// const refundOrderId = `FUR${new Date().getTime()}${Math.floor(Math.random() * 100)}`
// this.paymentService.setStrategy(PaymentMethod.MOMO)
// const refundMomoPaymentDto: RefundMomoPaymentDto = {
// orderId: refundOrderId,
// requestId: refundOrderId,
// amount: order.payment?.amount,
// transId: order.payment?.transaction['transId'],
// lang: 'vi',
// description: `Furnique - Hoàn tiền đơn hàng #${orderId}`
// }
// const refundedTransaction = await this.paymentService.refundTransaction(refundMomoPaymentDto)
// this.logger.log(JSON.stringify(refundedTransaction))

// 4. Fetch newest transaction of order
this.logger.log(`4. Fetch newest transaction of order`)
const queryMomoPaymentDto: QueryMomoPaymentDto = {
orderId: order.orderId,
requestId: order.orderId,
lang: 'vi'
}
const transaction = await this.paymentService.getTransaction(queryMomoPaymentDto)
this.logger.log(JSON.stringify(transaction))
// this.logger.log(`4. Fetch newest transaction of order`)
// const queryMomoPaymentDto: QueryMomoPaymentDto = {
// orderId: order.orderId,
// requestId: order.orderId,
// lang: 'vi'
// }
// const transaction = await this.paymentService.getTransaction(queryMomoPaymentDto)
// this.logger.log(JSON.stringify(transaction))

// 5. Update payment transactionStatus, transaction
this.logger.log(`5. Update payment transactionStatus, transaction`)
const payment = await this.paymentRepository.findOneAndUpdate(
{
_id: order.payment._id
},
{
$set: {
transactionStatus: TransactionStatus.REFUNDED,
transaction: transaction
},
$push: { transactionHistory: transaction }
},
{
session,
new: true
}
)
// this.logger.log(`5. Update payment transactionStatus, transaction`)
// const payment = await this.paymentRepository.findOneAndUpdate(
// {
// _id: order.payment._id
// },
// {
// $set: {
// transactionStatus: TransactionStatus.REFUNDED,
// transaction: transaction
// },
// // $push: { transactionHistory: transaction }
// },
// {
// session,
// new: true
// }
// )
// 6. Update order transactionStatus, payment
this.logger.log(`6. Update order transactionStatus, payment`)
await this.orderRepository.findOneAndUpdate(
{
_id: order._id
},
{
$set: {
transactionStatus: TransactionStatus.REFUNDED,
payment: payment
}
},
{
session
}
)
// this.logger.log(`6. Update order transactionStatus, payment`)
// await this.orderRepository.findOneAndUpdate(
// {
// _id: order._id
// },
// {
// $set: {
// transactionStatus: TransactionStatus.REFUNDED,
// payment: payment
// }
// },
// {
// session
// }
// )

// 7. Send email/notification to customer
this.logger.log(`7. Send email/notification to customer`)
Expand Down
14 changes: 14 additions & 0 deletions src/payment/contracts/constant.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum PaymentMethod {
PAY_OS = 'PAY_OS',
MOMO = 'MOMO',
ZALO_PAY = 'ZALO_PAY'
}
Expand All @@ -8,3 +9,16 @@ export enum MomoResultCode {
AUTHORIZED = 9000,
FAILED = 'FAILED'
}

export enum PayOSResultCode {
SUCCESS = '00',
FAILED = '01',
INVALID_PARAM = '02'
}

export enum PayOSStatus {
PENDING = 'PENDING',
PROCESSING = 'PROCESSING',
PAID = 'PAID',
CANCELLED = 'CANCELLED'
}
Loading

0 comments on commit ac8b980

Please sign in to comment.