摘要
作为一个商业系统,交易功能是必不可少的。本章介绍金币充值功能相关内容,从金币产品
-> 下订单
-> 支付
-> 回调处理
-> 金币入账
几个方面介绍
模块初始化
执行 NestJS CLI
三件套
nest g mo modules/coin
nest g co modules/coin --no-spec
nest g s modules/coin --no-spec
业务实现
1. 控制器
- 获取充值配置
pods
@Get('pods')
@ApiOperation({
summary: '获取充值配置',
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
type: BaseApiErrorResponse,
})
async pods() {
return await this.coinService.pods();
}
响应结果:
- 下订单
order
@Post('order')
@ApiOperation({
summary: '充值下单',
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
type: BaseApiErrorResponse,
})
@HttpCode(HttpStatus.OK)
async placeOrder(
@IRequestUser() user: IUserPayload,
@Body() data: CreateOrderDto,
) {
return await this.coinService.placeOrder(user, data);
}
- 订单查询
order/:id
@Get('order/:id')
@ApiOperation({
summary: '订单信息',
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
type: BaseApiErrorResponse,
})
@ApiParam({ name: 'id', type: Number, description: '订单ID' })
async order(
@IRequestUser() user: IUserPayload,
@Param('id', ParseIntPipe) id: number,
) {
return await this.coinService.orderInfo(user, id);
}
响应结果:
- 异步回调
notify/:provider
支付异步回调里,需要对外开放,并且响应数据不能按统一数据格式。因此配置了@Public()
和 @IgnoreTransform()
两个装饰器。@Public()
开放访问 , @IgnoreTransform()
忽略数据转换
@Post('notify/:provider')
@Public()
@IgnoreTransform()
@ApiOperation({
summary: '异步回调',
})
@HttpCode(HttpStatus.OK)
@ApiParam({ name: 'provider', type: String, description: '支付服务商' })
async notify(
@Param('provider') provider: string,
@Body() data: string,
@Res() res: Response,
) {
const result = await this.coinService.payCallback(provider, data);
// 这里需要直接输出
res.status(HttpStatus.OK).send(result);
return;
}
2. 实体
实体是一个功能的数据基础,每个模块必不可少。充值模块包含coin.entity
、 coin-transaction.entity
两个实体。
其中 coin.entity
定义了可进行充值的 金币产品,coin-transaction.entity
定义了 金币交易记录
具体如下
coin.entity
金币产品表
@Entity('coin')
export class CoinEntity {
@PrimaryGeneratedColumn({ type: 'int', name: 'id' })
id: number;
@Expose()
@Column({ type: 'varchar', name: 'name', comment: '产品名称' })
name: string;
@Column({
type: 'decimal',
name: 'coin',
comment: '金币',
transformer: {
to: (value: number) => value,
from: (value: string) => parseFloat(value),
},
})
coin: number;
@Column({
type: 'decimal',
name: 'gift_coin',
comment: '赠送金币',
transformer: {
to: (value: number) => value,
from: (value: string) => parseFloat(value),
},
})
giftCoin: number;
@Column({
type: 'decimal',
name: 'price',
comment: '单价',
transformer: {
to: (value: number) => value,
from: (value: string) => parseFloat(value),
},
})
price: number;
@Expose()
@Column({ type: 'tinyint', name: 'status' })
status: number;
@CreateDateColumn({ type: 'datetime', name: 'create_time' })
createTime: Date | string;
@UpdateDateColumn({ type: 'datetime', name: 'update_time' })
updateTime: Date | string;
}
coin-transaction.entity
金币交易记录表
@Entity('coin_transaction')
export class CoinTransactionEntity extends BaseEntity {
@PrimaryGeneratedColumn({ type: 'int', name: 'id' })
id: number;
@Column({ type: 'int', name: 'user_id', comment: '用户id' })
userId: number;
@Column({ type: 'int', name: 'coin_id', comment: '产品id' })
coinId: number;
@Column({ type: 'varchar', name: 'out_trade_no', comment: '交易单号' })
outTradeNo: string;
@Column({ type: 'varchar', name: 'subject', comment: '交易主题' })
subject: string;
@Column({ type: 'varchar', name: 'body', comment: '交易内容' })
body: string;
@Column({
type: 'decimal',
name: 'price',
comment: '单价',
transformer: {
to: (value: number) => value,
from: (value: string) => parseFloat(value),
},
})
price: number;
@Column({
type: 'decimal',
name: 'amount',
comment: '金额',
transformer: {
to: (value: number) => value,
from: (value: string) => parseFloat(value),
},
})
amount: number;
@Column({
type: 'decimal',
name: 'coin',
comment: '金币',
transformer: {
to: (value: number) => value,
from: (value: string) => parseFloat(value),
},
})
coin: number;
@Column({
type: 'datetime',
name: 'paid_time',
comment: '支付时间',
transformer: {
to: (value: any) => value,
from: (value: any) => dayjs(value).format(DATE_FORMAT),
},
})
paidTime: Date | string;
@Column({ type: 'tinyint', name: 'status' })
status: number;
@CreateDateColumn({ type: 'datetime', name: 'create_time' })
createTime: Date | string;
@UpdateDateColumn({ type: 'datetime', name: 'update_time' })
updateTime: Date | string;
}
3. 服务
- 下订单
placeOrder
用户在前台进行充值操作,提交充值产品 podId
传递给方法,查找对应产品并生成订单(交易记录)信息
而后进行订单支付产品
信息提交,目前只接入了支付宝
的 native
和 web
两种支付方式
响应结果:
- 异步回调
payCallback
支付宝异步回调中,需要注意的是使用支付宝公钥
进行数据验签。验签通过之后进行金币数据更新操作,金币操作的用户数据需要加锁
,系统使用了pessimistic_write
悲观锁写入。
- 查询订单
orderInfo
系统中,进行订单查询时,未支付的订单会优先进行支付状态检查(进行支付宝接口查询),保证支付状态实时性。
4. 超时队列
用户下订单,生成订单之后,需要推送一条异步队列事件。用于处理订单超时未支付,关闭订单操作。
-
服务内推送队列
-
processor
进行处理
更多代码请查看 certeasy_nest_open/src/modules/coin at main · CerteasyTeam/certeasy_nest_open (github.com)
系列文章
- Nestjs构建Certeasy证书自动化平台 - 介绍
- Nestjs构建Certeasy证书自动化平台 - 框架搭建
- Nestjs构建Certeasy证书自动化平台 - 业务实现(登录注册)
- Nestjs构建Certeasy证书自动化平台 - 业务实现(证书模块)
- Nestjs构建Certeasy证书自动化平台 - 业务实现(DNS授权模块)
- Nestjs构建Certeasy证书自动化平台 - 业务实现(云资源模块)
- Nestjs构建Certeasy证书自动化平台 - 业务实现(证书监控模块)
- Nestjs构建Certeasy证书自动化平台 - 业务实现(用户模块)
- Nestjs构建Certeasy证书自动化平台 - 业务实现(通知模块)
- Nestjs构建Certeasy证书自动化平台 - 业务实现(充值模块)
开源
联系
wechat: zuxcloud
Email: zuxing.xu@lettered.cn