- 添加支付模块架构,支持微信支付和抖音支付 - 创建支付订单、交易记录、退款记录数据表和实体 - 实现基于适配器模式的多平台支付集成 - 添加统一支付服务,集成会员订阅、积分购买、模板解锁业务 - 完善支付回调处理和退款功能 - 添加完整的DTO验证和API文档 - 集成到主应用模块,完成依赖注入配置
494 lines
12 KiB
TypeScript
494 lines
12 KiB
TypeScript
import { MigrationInterface, QueryRunner, Table, Index } from 'typeorm';
|
|
|
|
export class CreatePaymentTables1756900000000 implements MigrationInterface {
|
|
name = 'CreatePaymentTables1756900000000';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
// 创建支付订单表
|
|
await queryRunner.createTable(
|
|
new Table({
|
|
name: 'payment_orders',
|
|
columns: [
|
|
{
|
|
name: 'id',
|
|
type: 'varchar',
|
|
length: '36',
|
|
isPrimary: true,
|
|
generationStrategy: 'uuid',
|
|
},
|
|
{
|
|
name: 'userId',
|
|
type: 'varchar',
|
|
length: '36',
|
|
},
|
|
{
|
|
name: 'platform',
|
|
type: 'enum',
|
|
enum: ['wechat', 'douyin'],
|
|
enumName: 'platform_type',
|
|
},
|
|
{
|
|
name: 'orderNo',
|
|
type: 'varchar',
|
|
length: '64',
|
|
isUnique: true,
|
|
},
|
|
{
|
|
name: 'thirdPartyOrderId',
|
|
type: 'varchar',
|
|
length: '100',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'paymentMethod',
|
|
type: 'enum',
|
|
enum: ['wechat_miniprogram', 'bytedance_miniprogram'],
|
|
},
|
|
{
|
|
name: 'status',
|
|
type: 'enum',
|
|
enum: [
|
|
'pending',
|
|
'paid',
|
|
'confirmed',
|
|
'cancelled',
|
|
'failed',
|
|
'refunded',
|
|
],
|
|
default: "'pending'",
|
|
},
|
|
{
|
|
name: 'businessType',
|
|
type: 'enum',
|
|
enum: ['subscription', 'credit_purchase', 'template_unlock'],
|
|
},
|
|
{
|
|
name: 'businessId',
|
|
type: 'varchar',
|
|
length: '100',
|
|
},
|
|
{
|
|
name: 'description',
|
|
type: 'varchar',
|
|
length: '200',
|
|
},
|
|
{
|
|
name: 'amount',
|
|
type: 'bigint',
|
|
},
|
|
{
|
|
name: 'paidAmount',
|
|
type: 'bigint',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'currency',
|
|
type: 'varchar',
|
|
length: '10',
|
|
default: "'CNY'",
|
|
},
|
|
{
|
|
name: 'platformUserId',
|
|
type: 'varchar',
|
|
length: '100',
|
|
},
|
|
{
|
|
name: 'paymentParams',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'paidAt',
|
|
type: 'timestamp',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'expiredAt',
|
|
type: 'timestamp',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'notifyUrl',
|
|
type: 'varchar',
|
|
length: '500',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'metadata',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'errorMessage',
|
|
type: 'text',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'createdAt',
|
|
type: 'timestamp',
|
|
default: 'CURRENT_TIMESTAMP',
|
|
},
|
|
{
|
|
name: 'updatedAt',
|
|
type: 'timestamp',
|
|
default: 'CURRENT_TIMESTAMP',
|
|
onUpdate: 'CURRENT_TIMESTAMP',
|
|
},
|
|
],
|
|
foreignKeys: [
|
|
{
|
|
columnNames: ['userId'],
|
|
referencedColumnNames: ['id'],
|
|
referencedTableName: 'users',
|
|
onDelete: 'CASCADE',
|
|
},
|
|
],
|
|
}),
|
|
true,
|
|
);
|
|
|
|
// 创建支付交易记录表
|
|
await queryRunner.createTable(
|
|
new Table({
|
|
name: 'payment_transactions',
|
|
columns: [
|
|
{
|
|
name: 'id',
|
|
type: 'varchar',
|
|
length: '36',
|
|
isPrimary: true,
|
|
generationStrategy: 'uuid',
|
|
},
|
|
{
|
|
name: 'paymentOrderId',
|
|
type: 'varchar',
|
|
length: '36',
|
|
},
|
|
{
|
|
name: 'thirdPartyTransactionId',
|
|
type: 'varchar',
|
|
length: '100',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'thirdPartyOrderId',
|
|
type: 'varchar',
|
|
length: '100',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'transactionType',
|
|
type: 'enum',
|
|
enum: ['payment', 'refund', 'callback'],
|
|
},
|
|
{
|
|
name: 'status',
|
|
type: 'enum',
|
|
enum: ['pending', 'success', 'failed'],
|
|
default: "'pending'",
|
|
},
|
|
{
|
|
name: 'amount',
|
|
type: 'bigint',
|
|
},
|
|
{
|
|
name: 'currency',
|
|
type: 'varchar',
|
|
length: '10',
|
|
default: "'CNY'",
|
|
},
|
|
{
|
|
name: 'transactionTime',
|
|
type: 'timestamp',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'description',
|
|
type: 'varchar',
|
|
length: '200',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'requestData',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'responseData',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'callbackData',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'errorCode',
|
|
type: 'varchar',
|
|
length: '50',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'errorMessage',
|
|
type: 'text',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'processCount',
|
|
type: 'int',
|
|
default: 1,
|
|
},
|
|
{
|
|
name: 'lastProcessedAt',
|
|
type: 'timestamp',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'metadata',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'createdAt',
|
|
type: 'timestamp',
|
|
default: 'CURRENT_TIMESTAMP',
|
|
},
|
|
{
|
|
name: 'updatedAt',
|
|
type: 'timestamp',
|
|
default: 'CURRENT_TIMESTAMP',
|
|
onUpdate: 'CURRENT_TIMESTAMP',
|
|
},
|
|
],
|
|
foreignKeys: [
|
|
{
|
|
columnNames: ['paymentOrderId'],
|
|
referencedColumnNames: ['id'],
|
|
referencedTableName: 'payment_orders',
|
|
onDelete: 'CASCADE',
|
|
},
|
|
],
|
|
}),
|
|
true,
|
|
);
|
|
|
|
// 创建退款记录表
|
|
await queryRunner.createTable(
|
|
new Table({
|
|
name: 'refund_records',
|
|
columns: [
|
|
{
|
|
name: 'id',
|
|
type: 'varchar',
|
|
length: '36',
|
|
isPrimary: true,
|
|
generationStrategy: 'uuid',
|
|
},
|
|
{
|
|
name: 'paymentOrderId',
|
|
type: 'varchar',
|
|
length: '36',
|
|
},
|
|
{
|
|
name: 'refundNo',
|
|
type: 'varchar',
|
|
length: '64',
|
|
isUnique: true,
|
|
},
|
|
{
|
|
name: 'thirdPartyRefundId',
|
|
type: 'varchar',
|
|
length: '100',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'thirdPartyTransactionId',
|
|
type: 'varchar',
|
|
length: '100',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'status',
|
|
type: 'enum',
|
|
enum: ['pending', 'processing', 'success', 'failed'],
|
|
default: "'pending'",
|
|
},
|
|
{
|
|
name: 'refundAmount',
|
|
type: 'bigint',
|
|
},
|
|
{
|
|
name: 'originalAmount',
|
|
type: 'bigint',
|
|
},
|
|
{
|
|
name: 'currency',
|
|
type: 'varchar',
|
|
length: '10',
|
|
default: "'CNY'",
|
|
},
|
|
{
|
|
name: 'reason',
|
|
type: 'varchar',
|
|
length: '500',
|
|
},
|
|
{
|
|
name: 'description',
|
|
type: 'text',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'requestedBy',
|
|
type: 'varchar',
|
|
length: '36',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'approvedBy',
|
|
type: 'varchar',
|
|
length: '36',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'refundedAt',
|
|
type: 'timestamp',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'arrivedAt',
|
|
type: 'timestamp',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'estimatedArrivalTime',
|
|
type: 'timestamp',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'failureReason',
|
|
type: 'text',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'errorCode',
|
|
type: 'varchar',
|
|
length: '50',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'requestData',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'responseData',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'callbackData',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'processCount',
|
|
type: 'int',
|
|
default: 1,
|
|
},
|
|
{
|
|
name: 'lastProcessedAt',
|
|
type: 'timestamp',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'metadata',
|
|
type: 'json',
|
|
isNullable: true,
|
|
},
|
|
{
|
|
name: 'createdAt',
|
|
type: 'timestamp',
|
|
default: 'CURRENT_TIMESTAMP',
|
|
},
|
|
{
|
|
name: 'updatedAt',
|
|
type: 'timestamp',
|
|
default: 'CURRENT_TIMESTAMP',
|
|
onUpdate: 'CURRENT_TIMESTAMP',
|
|
},
|
|
],
|
|
foreignKeys: [
|
|
{
|
|
columnNames: ['paymentOrderId'],
|
|
referencedColumnNames: ['id'],
|
|
referencedTableName: 'payment_orders',
|
|
onDelete: 'CASCADE',
|
|
},
|
|
],
|
|
}),
|
|
true,
|
|
);
|
|
|
|
// 创建索引
|
|
await this.createIndexes(queryRunner);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
// 删除表(按依赖关系倒序)
|
|
await queryRunner.dropTable('refund_records');
|
|
await queryRunner.dropTable('payment_transactions');
|
|
await queryRunner.dropTable('payment_orders');
|
|
}
|
|
|
|
private async createIndexes(queryRunner: QueryRunner): Promise<void> {
|
|
// payment_orders 表索引
|
|
await queryRunner.query(
|
|
`CREATE UNIQUE INDEX IDX_PAYMENT_ORDERS_ORDER_NO ON payment_orders (orderNo)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_PAYMENT_ORDERS_USER_STATUS ON payment_orders (userId, status)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_PAYMENT_ORDERS_PLATFORM_BUSINESS ON payment_orders (platform, businessType)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_PAYMENT_ORDERS_CREATED_AT ON payment_orders (createdAt)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_PAYMENT_ORDERS_THIRD_PARTY_ID ON payment_orders (thirdPartyOrderId)`,
|
|
);
|
|
|
|
// payment_transactions 表索引
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_PAYMENT_TRANSACTIONS_ORDER_ID ON payment_transactions (paymentOrderId)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_PAYMENT_TRANSACTIONS_THIRD_PARTY_ID ON payment_transactions (thirdPartyTransactionId)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_PAYMENT_TRANSACTIONS_TYPE_STATUS ON payment_transactions (transactionType, status)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_PAYMENT_TRANSACTIONS_CREATED_AT ON payment_transactions (createdAt)`,
|
|
);
|
|
|
|
// refund_records 表索引
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_REFUND_RECORDS_ORDER_ID ON refund_records (paymentOrderId)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE UNIQUE INDEX IDX_REFUND_RECORDS_REFUND_NO ON refund_records (refundNo)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_REFUND_RECORDS_THIRD_PARTY_ID ON refund_records (thirdPartyRefundId)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_REFUND_RECORDS_STATUS ON refund_records (status)`,
|
|
);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IDX_REFUND_RECORDS_CREATED_AT ON refund_records (createdAt)`,
|
|
);
|
|
}
|
|
}
|