Files
server/app/services/payment_service.go
T
2026-01-16 15:49:34 +08:00

1088 lines
34 KiB
Go

package services
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/dromara/carbon/v2"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"github.com/go-pay/gopay/wechat/v3"
"github.com/goravel/framework/contracts/database/orm"
"github.com/goravel/framework/facades"
apperrors "goravel/app/errors"
"goravel/app/models"
"goravel/app/utils"
"goravel/app/utils/errorlog"
"goravel/database/migrations"
)
type PaymentService interface {
// GetPaymentMethodByID 根据ID获取支付方式
GetPaymentMethodByID(id uint) (*models.PaymentMethod, error)
// GetPaymentMethodByCode 根据代码获取支付方式
GetPaymentMethodByCode(code string) (*models.PaymentMethod, error)
// GetPaymentMethods 获取支付方式列表
GetPaymentMethods(filters PaymentMethodFilters, page, pageSize int) ([]models.PaymentMethod, int64, error)
// CreatePaymentMethod 创建支付方式
CreatePaymentMethod(name, code, paymentType string, config map[string]any, isActive bool, sort int, description string) (*models.PaymentMethod, error)
// UpdatePaymentMethod 更新支付方式(保留兼容)
UpdatePaymentMethod(id uint, name string, config map[string]any, isActive bool, sort int, description string) error
// Update 更新支付方式(新模式)
UpdatePaymentMethodModel(paymentMethod *models.PaymentMethod) error
// DeletePaymentMethod 删除支付方式
DeletePaymentMethod(id uint) error
// GetPaymentByID 根据ID获取支付记录
GetPaymentByID(id uint) (*models.Payment, error)
// GetPaymentByPaymentNo 根据支付单号获取支付记录
GetPaymentByPaymentNo(paymentNo string) (*models.Payment, error)
// GetPayments 获取支付记录列表
GetPayments(filters PaymentFilters, page, pageSize int) ([]models.Payment, int64, error)
// CreatePayment 创建支付记录
CreatePayment(orderNo string, paymentMethodID uint, userID uint, amount float64, remark string) (*models.Payment, error)
// UpdatePaymentStatus 更新支付状态(paymentNo 可选,提供则更快定位分表)
UpdatePaymentStatus(paymentID uint, status string, thirdPartyNo string, payTime *time.Time, failReason string, notifyData map[string]any, paymentNo ...string) error
// CreatePaymentOrder 创建支付订单(调用第三方支付)
CreatePaymentOrder(payment *models.Payment, clientIP string) (map[string]any, error)
// QueryPaymentOrder 查询支付订单状态
QueryPaymentOrder(payment *models.Payment) (map[string]any, error)
// HandlePaymentNotify 处理支付回调通知
HandlePaymentNotify(paymentMethod *models.PaymentMethod, notifyData map[string]any) (*models.Payment, error)
}
// PaymentMethodFilters 支付方式查询过滤器
type PaymentMethodFilters struct {
Name string
Code string
Type string
IsActive string
Description string
OrderBy string
}
// PaymentFilters 支付记录查询过滤器
type PaymentFilters struct {
PaymentNo string
OrderNo string
PaymentMethodID uint
UserID uint
Status string
StartTime time.Time
EndTime time.Time
OrderBy string
}
// PaymentCountThreshold 支付记录分页统计优化阈值(超过此值使用执行计划估算)
const PaymentCountThreshold int64 = 100000
// BuildPaymentQuery 构建支付记录分表查询(包含时间范围 + 通用筛选),供列表查询/导出复用
func BuildPaymentQuery(tableName string, filters PaymentFilters) orm.Query {
query := facades.Orm().Query().Table(tableName).Where("deleted_at IS NULL")
// 时间范围
if !filters.StartTime.IsZero() {
query = query.Where("created_at >= ?", utils.FormatDateTime(filters.StartTime))
}
if !filters.EndTime.IsZero() {
query = query.Where("created_at <= ?", utils.FormatDateTime(filters.EndTime))
}
// 精确匹配条件
if filters.PaymentNo != "" {
query = query.Where("payment_no = ?", filters.PaymentNo)
}
if filters.OrderNo != "" {
query = query.Where("order_no = ?", filters.OrderNo)
}
if filters.PaymentMethodID > 0 {
query = query.Where("payment_method_id", filters.PaymentMethodID)
}
if filters.UserID > 0 {
query = query.Where("user_id", filters.UserID)
}
if filters.Status != "" {
query = query.Where("status", filters.Status)
}
return query
}
type PaymentServiceImpl struct {
shardingService ShardingService
shardingQueryService ShardingQueryService
}
func NewPaymentService() PaymentService {
service := &PaymentServiceImpl{
shardingService: NewShardingService(),
}
// 初始化分表查询服务
service.shardingQueryService = NewShardingQueryService(ShardingQueryConfig{
BaseTableName: "payments",
GetColumns: func() string {
return service.getPaymentTableColumns()
},
BuildWhereClause: func(filters any) (string, []any) {
return service.buildPaymentShardingWhereClause(filters)
},
GetAllowedOrderFields: func() map[string]bool {
return map[string]bool{
"id": true,
"payment_no": true,
"order_no": true,
"user_id": true,
"amount": true,
"status": true,
"created_at": true,
"updated_at": true,
}
},
DefaultOrderBy: "created_at:desc",
ModuleName: "payment",
CountThreshold: PaymentCountThreshold,
})
return service
}
// GetPaymentMethodByID 根据ID获取支付方式
func (s *PaymentServiceImpl) GetPaymentMethodByID(id uint) (*models.PaymentMethod, error) {
var paymentMethod models.PaymentMethod
if err := facades.Orm().Query().Where("id", id).FirstOrFail(&paymentMethod); err != nil {
return nil, apperrors.ErrPaymentMethodNotFound.WithError(err)
}
return &paymentMethod, nil
}
// GetPaymentMethodByCode 根据代码获取支付方式
func (s *PaymentServiceImpl) GetPaymentMethodByCode(code string) (*models.PaymentMethod, error) {
var paymentMethod models.PaymentMethod
if err := facades.Orm().Query().Where("code", code).Where("is_active", true).FirstOrFail(&paymentMethod); err != nil {
return nil, apperrors.ErrPaymentMethodNotFound.WithError(err)
}
return &paymentMethod, nil
}
// GetPaymentMethods 获取支付方式列表
func (s *PaymentServiceImpl) GetPaymentMethods(filters PaymentMethodFilters, page, pageSize int) ([]models.PaymentMethod, int64, error) {
query := facades.Orm().Query().Model(&models.PaymentMethod{})
// 应用筛选条件
if filters.Name != "" {
query = query.Where("name LIKE ?", "%"+filters.Name+"%")
}
if filters.Code != "" {
query = query.Where("code", filters.Code)
}
if filters.Type != "" {
query = query.Where("type", filters.Type)
}
if filters.IsActive != "" {
switch filters.IsActive {
case "1":
query = query.Where("is_active", true)
case "0":
query = query.Where("is_active", false)
}
}
// 应用排序
if filters.OrderBy != "" {
query = s.applyOrderBy(query, filters.OrderBy)
} else {
query = query.Order("sort asc").Order("id desc")
}
// 分页查询
var paymentMethods []models.PaymentMethod
var total int64
if err := query.Paginate(page, pageSize, &paymentMethods, &total); err != nil {
return nil, 0, apperrors.ErrQueryFailed.WithError(err)
}
return paymentMethods, total, nil
}
// CreatePaymentMethod 创建支付方式
func (s *PaymentServiceImpl) CreatePaymentMethod(name, code, paymentType string, config map[string]any, isActive bool, sort int, description string) (*models.PaymentMethod, error) {
// 检查代码是否已存在
exists, err := facades.Orm().Query().Model(&models.PaymentMethod{}).Where("code", code).Exists()
if err != nil {
return nil, apperrors.ErrCreateFailed.WithError(err)
}
if exists {
return nil, apperrors.ErrPaymentMethodCodeExists
}
// 序列化配置
configJSON, err := json.Marshal(config)
if err != nil {
return nil, apperrors.ErrPaymentConfigRequired.WithError(err)
}
// 使用 map 创建,确保 IsActive 为 false 时也能正确保存
// GORM 在处理结构体时会忽略零值字段,使用 map 可以确保所有字段都被保存
now := carbon.Now()
paymentMethod := &models.PaymentMethod{}
createData := map[string]any{
"name": name,
"code": code,
"type": paymentType,
"config": string(configJSON),
"is_active": isActive,
"sort": sort,
"description": description,
"created_at": now,
"updated_at": now,
}
if err := facades.Orm().Query().Model(paymentMethod).Create(createData); err != nil {
return nil, apperrors.ErrCreateFailed.WithError(err)
}
return paymentMethod, nil
}
// UpdatePaymentMethod 更新支付方式
func (s *PaymentServiceImpl) UpdatePaymentMethod(id uint, name string, config map[string]any, isActive bool, sort int, description string) error {
paymentMethod, err := s.GetPaymentMethodByID(id)
if err != nil {
return err
}
// 序列化配置
var configJSON string
if config != nil {
configBytes, err := json.Marshal(config)
if err != nil {
return apperrors.ErrPaymentConfigRequired.WithError(err)
}
configJSON = string(configBytes)
} else {
configJSON = paymentMethod.Config // 保持原有配置
}
updateData := map[string]any{
"name": name,
"config": configJSON,
"is_active": isActive,
"sort": sort,
"description": description,
}
if _, err := facades.Orm().Query().Where("id", id).Update(&models.PaymentMethod{}, updateData); err != nil {
return apperrors.ErrUpdateFailed.WithError(err)
}
return nil
}
// UpdatePaymentMethodModel 更新支付方式(新模式)
func (s *PaymentServiceImpl) UpdatePaymentMethodModel(paymentMethod *models.PaymentMethod) error {
if err := facades.Orm().Query().Save(paymentMethod); err != nil {
return apperrors.ErrUpdateFailed.WithError(err)
}
return nil
}
// DeletePaymentMethod 删除支付方式
func (s *PaymentServiceImpl) DeletePaymentMethod(id uint) error {
_, err := s.GetPaymentMethodByID(id)
if err != nil {
return err
}
if _, err := facades.Orm().Query().Where("id", id).Delete(&models.PaymentMethod{}); err != nil {
return apperrors.ErrDeleteFailed.WithError(err)
}
return nil
}
// GetPaymentByID 根据ID获取支付记录(支持分表)
func (s *PaymentServiceImpl) GetPaymentByID(id uint) (*models.Payment, error) {
// 使用分表查找
payment, err := s.findPaymentByID(id)
if err != nil {
return nil, err
}
// 手动加载支付方式
if payment.PaymentMethodID > 0 {
paymentMethod, err := s.GetPaymentMethodByID(payment.PaymentMethodID)
if err == nil {
payment.PaymentMethod = *paymentMethod
}
}
return payment, nil
}
// GetPaymentByPaymentNo 根据支付单号获取支付记录(支持分表,直接定位更高效)
func (s *PaymentServiceImpl) GetPaymentByPaymentNo(paymentNo string) (*models.Payment, error) {
// 使用分表查找(通过支付单号直接定位分表)
payment, err := s.findPaymentByPaymentNo(paymentNo)
if err != nil {
return nil, err
}
// 手动加载支付方式
if payment.PaymentMethodID > 0 {
paymentMethod, err := s.GetPaymentMethodByID(payment.PaymentMethodID)
if err == nil {
payment.PaymentMethod = *paymentMethod
}
}
return payment, nil
}
// GetPayments 获取支付记录列表(支持分表,限制不超过3个月)
func (s *PaymentServiceImpl) GetPayments(filters PaymentFilters, page, pageSize int) ([]models.Payment, int64, error) {
// 验证时间范围不超过3个月
valid, err := utils.ValidateTimeRange(filters.StartTime, filters.EndTime)
if !valid {
return nil, 0, err
}
// 获取需要查询的所有分表
tableNames := utils.GetShardingTableNames("payments", filters.StartTime, filters.EndTime)
if len(tableNames) == 0 {
return []models.Payment{}, 0, nil
}
var payments []models.Payment
var total int64
// 如果只有一个分表,直接查询
if len(tableNames) == 1 {
payments, total, err = s.querySinglePaymentTable(tableNames[0], filters, page, pageSize)
} else {
// 多个分表:使用 UNION ALL 在数据库层面合并
payments, total, err = s.queryMultiplePaymentTablesWithUnion(tableNames, filters, page, pageSize)
}
if err != nil {
return nil, 0, apperrors.ErrQueryFailed.WithError(err)
}
// 批量加载支付方式
paymentMethodIDs := make(map[uint]bool)
for _, payment := range payments {
if payment.PaymentMethodID > 0 {
paymentMethodIDs[payment.PaymentMethodID] = true
}
}
// 批量查询支付方式
paymentMethodsMap := make(map[uint]*models.PaymentMethod)
if len(paymentMethodIDs) > 0 {
var ids []uint
for id := range paymentMethodIDs {
ids = append(ids, id)
}
// 转换为 []any
idsAny := make([]any, len(ids))
for i, id := range ids {
idsAny[i] = id
}
var paymentMethods []models.PaymentMethod
if err := facades.Orm().Query().Model(&models.PaymentMethod{}).WhereIn("id", idsAny).Find(&paymentMethods); err == nil {
for i := range paymentMethods {
paymentMethodsMap[paymentMethods[i].ID] = &paymentMethods[i]
}
}
}
// 关联支付方式
for i := range payments {
if pm, ok := paymentMethodsMap[payments[i].PaymentMethodID]; ok {
payments[i].PaymentMethod = *pm
}
}
return payments, total, nil
}
// CreatePayment 创建支付记录(写入分表)
func (s *PaymentServiceImpl) CreatePayment(orderNo string, paymentMethodID uint, userID uint, amount float64, remark string) (*models.Payment, error) {
// 验证金额
if amount <= 0 {
return nil, apperrors.ErrPaymentAmountInvalid
}
// 验证支付方式
paymentMethod, err := s.GetPaymentMethodByID(paymentMethodID)
if err != nil {
return nil, err
}
if !paymentMethod.IsActive {
return nil, apperrors.ErrPaymentMethodDisabled
}
// 当前时间用于分表
now := time.Now()
// 确保分表存在
tableName, err := s.ensurePaymentShardingTableExists(now)
if err != nil {
return nil, apperrors.ErrCreatePaymentFailed.WithError(err)
}
// 生成支付单号(包含日期,便于后续定位分表)
paymentNo := utils.GenerateShardingNo(utils.PaymentNoConfig)
payment := &models.Payment{
PaymentNo: paymentNo,
OrderNo: orderNo,
PaymentMethodID: paymentMethodID,
UserID: userID,
Amount: amount,
Status: "pending",
Remark: remark,
}
// 写入分表
if err := facades.Orm().Query().Table(tableName).Create(payment); err != nil {
errorlog.Record(context.Background(), "payment", "创建支付记录失败", map[string]any{
"table_name": tableName,
"order_no": orderNo,
"payment_method_id": paymentMethodID,
"user_id": userID,
"amount": amount,
"error": err.Error(),
}, "创建支付记录失败: %v", err)
return nil, apperrors.ErrCreatePaymentFailed.WithError(err)
}
return payment, nil
}
// UpdatePaymentStatus 更新支付状态(支持分表)
// paymentNo 可选,如果提供则可以更快定位分表
func (s *PaymentServiceImpl) UpdatePaymentStatus(paymentID uint, status string, thirdPartyNo string, payTime *time.Time, failReason string, notifyData map[string]any, paymentNo ...string) error {
// 先查找支付记录以确定分表
var payment *models.Payment
var err error
if len(paymentNo) > 0 && paymentNo[0] != "" {
payment, err = s.findPaymentByPaymentNo(paymentNo[0])
} else {
payment, err = s.findPaymentByID(paymentID)
}
if err != nil {
return apperrors.ErrPaymentNotFound.WithError(err)
}
// 确定分表名
timeStr := payment.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
tableName := utils.GetShardingTableName("payments", createdAt)
updateData := map[string]any{
"status": status,
}
if thirdPartyNo != "" {
updateData["third_party_no"] = thirdPartyNo
}
if payTime != nil {
updateData["pay_time"] = payTime
}
if failReason != "" {
updateData["fail_reason"] = failReason
}
if notifyData != nil {
notifyJSON, err := json.Marshal(notifyData)
if err == nil {
updateData["notify_data"] = string(notifyJSON)
}
}
if _, err := facades.Orm().Query().Table(tableName).Where("id", payment.ID).Update(&models.Payment{}, updateData); err != nil {
return apperrors.ErrUpdateFailed.WithError(err)
}
return nil
}
// CreatePaymentOrder 创建支付订单(调用第三方支付)
func (s *PaymentServiceImpl) CreatePaymentOrder(payment *models.Payment, clientIP string) (map[string]any, error) {
// 获取支付方式
paymentMethod, err := s.GetPaymentMethodByID(payment.PaymentMethodID)
if err != nil {
return nil, err
}
// 解析配置
var config map[string]any
if err := json.Unmarshal([]byte(paymentMethod.Config), &config); err != nil {
return nil, apperrors.ErrPaymentConfigRequired.WithError(err)
}
// 根据支付类型调用不同的支付接口
switch paymentMethod.Type {
case "wechat":
return s.createWechatPayment(payment, config, clientIP)
case "alipay":
return s.createAlipayPayment(payment, config, clientIP)
default:
return nil, apperrors.ErrInvalidPaymentType.WithMessage(fmt.Sprintf("不支持的支付类型: %s", paymentMethod.Type))
}
}
// createWechatPayment 创建微信支付订单
func (s *PaymentServiceImpl) createWechatPayment(payment *models.Payment, config map[string]any, clientIP string) (map[string]any, error) {
// 这里需要根据 gopay 的微信支付文档实现
// 示例代码,实际使用时需要根据 gopay 的最新 API 调整
// 参考: https://github.com/go-pay/gopay/tree/main/wechat/v3
// 获取配置参数
appID, _ := config["app_id"].(string)
mchID, _ := config["mch_id"].(string)
apiV3Key, _ := config["api_v3_key"].(string)
certSerialNo, _ := config["cert_serial_no"].(string)
privateKeyPath, _ := config["private_key_path"].(string)
if appID == "" || mchID == "" || apiV3Key == "" {
return nil, apperrors.ErrPaymentConfigRequired.WithMessage("微信支付配置不完整")
}
// 创建微信支付客户端
client, err := wechat.NewClientV3(appID, mchID, apiV3Key, certSerialNo)
if err != nil {
return nil, apperrors.ErrCreatePaymentFailed.WithError(err)
}
// 设置私钥(如果需要)
if privateKeyPath != "" {
// 这里需要根据 gopay 的实际 API 设置私钥
// client.SetPrivateKey(...)
}
// 设置回调地址(需要从配置中读取)
notifyURL, _ := config["notify_url"].(string)
if notifyURL == "" {
notifyURL = fmt.Sprintf("%s/api/payment/notify/wechat", facades.Config().GetString("app.url"))
}
// 创建支付订单
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", payment.PaymentNo)
bm.Set("description", payment.Remark)
bm.Set("amount", map[string]any{
"total": int(payment.Amount * 100), // 转换为分
"currency": "CNY",
})
bm.Set("notify_url", notifyURL)
bm.Set("payer", map[string]any{
"openid": config["openid"], // 需要从订单或用户信息中获取
})
// 注意:这里需要根据 gopay 的最新 API 调用
// 示例代码,实际使用时需要根据 gopay 的最新文档调整
ctx := context.Background()
wxRsp, err := client.V3TransactionJsapi(ctx, bm)
if err != nil {
return nil, apperrors.ErrCreatePaymentFailed.WithError(err)
}
if wxRsp.Code != wechat.Success {
return nil, apperrors.ErrCreatePaymentFailed.WithMessage(wxRsp.Error)
}
return map[string]any{
"payment_no": payment.PaymentNo,
"prepay_id": wxRsp.Response.PrepayId,
// PaySign 可能需要从其他地方获取或计算
}, nil
}
// createAlipayPayment 创建支付宝支付订单
func (s *PaymentServiceImpl) createAlipayPayment(payment *models.Payment, config map[string]any, clientIP string) (map[string]any, error) {
// 这里需要根据 gopay 的支付宝文档实现
// 示例代码,实际使用时需要根据 gopay 的最新 API 调整
// 参考: https://github.com/go-pay/gopay/tree/main/alipay
// 获取配置参数
appID, _ := config["app_id"].(string)
privateKey, _ := config["private_key"].(string)
// appCertPublicKey, _ := config["app_cert_public_key"].(string)
// alipayRootCert, _ := config["alipay_root_cert"].(string)
// alipayPublicCert, _ := config["alipay_public_cert"].(string)
if appID == "" || privateKey == "" {
return nil, apperrors.ErrPaymentConfigRequired.WithMessage("支付宝配置不完整")
}
// 创建支付宝客户端
client, err := alipay.NewClient(appID, privateKey, false)
if err != nil {
return nil, apperrors.ErrCreatePaymentFailed.WithError(err)
}
// 设置证书(如果需要)
// 注意:这里需要根据 gopay 的最新 API 设置证书
// 示例代码,实际使用时需要根据 gopay 的最新文档调整
// if appCertPublicKey != "" {
// client.SetAppCertPublicKey(appCertPublicKey)
// }
// if alipayRootCert != "" {
// client.SetAlipayRootCert(alipayRootCert)
// }
// if alipayPublicCert != "" {
// client.SetAlipayPublicCert(alipayPublicCert)
// }
// 设置回调地址
notifyURL, _ := config["notify_url"].(string)
if notifyURL == "" {
notifyURL = fmt.Sprintf("%s/api/payment/notify/alipay", facades.Config().GetString("app.url"))
}
client.SetNotifyUrl(notifyURL)
// 创建支付订单
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", payment.PaymentNo)
bm.Set("subject", payment.Remark)
bm.Set("total_amount", fmt.Sprintf("%.2f", payment.Amount))
bm.Set("product_code", "QUICK_MSECURITY_PAY")
ctx := context.Background()
payUrl, err := client.TradeAppPay(ctx, bm)
if err != nil {
return nil, apperrors.ErrCreatePaymentFailed.WithError(err)
}
return map[string]any{
"payment_no": payment.PaymentNo,
"pay_url": payUrl,
}, nil
}
// QueryPaymentOrder 查询支付订单状态
func (s *PaymentServiceImpl) QueryPaymentOrder(payment *models.Payment) (map[string]any, error) {
// 获取支付方式
paymentMethod, err := s.GetPaymentMethodByID(payment.PaymentMethodID)
if err != nil {
return nil, err
}
// 解析配置
var config map[string]any
if err := json.Unmarshal([]byte(paymentMethod.Config), &config); err != nil {
return nil, apperrors.ErrPaymentConfigRequired.WithError(err)
}
// 根据支付类型查询
switch paymentMethod.Type {
case "wechat":
return s.queryWechatPayment(payment, config)
case "alipay":
return s.queryAlipayPayment(payment, config)
default:
return nil, apperrors.ErrInvalidPaymentType.WithMessage(fmt.Sprintf("不支持的支付类型: %s", paymentMethod.Type))
}
}
// queryWechatPayment 查询微信支付订单状态
func (s *PaymentServiceImpl) queryWechatPayment(payment *models.Payment, config map[string]any) (map[string]any, error) {
// 实现微信支付查询逻辑
// 这里需要根据 gopay 的微信支付文档实现
return nil, fmt.Errorf("微信支付查询功能待实现")
}
// queryAlipayPayment 查询支付宝支付订单状态
func (s *PaymentServiceImpl) queryAlipayPayment(payment *models.Payment, config map[string]any) (map[string]any, error) {
// 实现支付宝支付查询逻辑
// 这里需要根据 gopay 的支付宝文档实现
return nil, fmt.Errorf("支付宝支付查询功能待实现")
}
// HandlePaymentNotify 处理支付回调通知
func (s *PaymentServiceImpl) HandlePaymentNotify(paymentMethod *models.PaymentMethod, notifyData map[string]any) (*models.Payment, error) {
// 根据支付类型处理回调
switch paymentMethod.Type {
case "wechat":
return s.handleWechatNotify(paymentMethod, notifyData)
case "alipay":
return s.handleAlipayNotify(paymentMethod, notifyData)
default:
return nil, apperrors.ErrInvalidPaymentType.WithMessage(fmt.Sprintf("不支持的支付类型: %s", paymentMethod.Type))
}
}
// handleWechatNotify 处理微信支付回调
func (s *PaymentServiceImpl) handleWechatNotify(paymentMethod *models.PaymentMethod, notifyData map[string]any) (*models.Payment, error) {
// 实现微信支付回调处理逻辑
// 这里需要根据 gopay 的微信支付文档实现
return nil, fmt.Errorf("微信支付回调处理功能待实现")
}
// handleAlipayNotify 处理支付宝支付回调
func (s *PaymentServiceImpl) handleAlipayNotify(paymentMethod *models.PaymentMethod, notifyData map[string]any) (*models.Payment, error) {
// 实现支付宝支付回调处理逻辑
// 这里需要根据 gopay 的支付宝文档实现
return nil, fmt.Errorf("支付宝支付回调处理功能待实现")
}
// buildPaymentMethodWhereClause 构建支付方式查询的 WHERE 条件
func (s *PaymentServiceImpl) buildPaymentMethodWhereClause(filters PaymentMethodFilters) (string, []any) {
var conditions []string
var args []any
if filters.Name != "" {
conditions = append(conditions, "name LIKE ?")
args = append(args, "%"+filters.Name+"%")
}
if filters.Code != "" {
conditions = append(conditions, "code = ?")
args = append(args, filters.Code)
}
if filters.Type != "" {
conditions = append(conditions, "type = ?")
args = append(args, filters.Type)
}
if filters.IsActive != "" {
switch filters.IsActive {
case "1":
conditions = append(conditions, "is_active = ?")
args = append(args, true)
case "0":
conditions = append(conditions, "is_active = ?")
args = append(args, false)
}
}
if filters.Description != "" {
conditions = append(conditions, "description LIKE ?")
args = append(args, "%"+filters.Description+"%")
}
if len(conditions) == 0 {
return "", nil
}
return strings.Join(conditions, " AND "), args
}
// buildPaymentWhereClause 构建支付记录查询的 WHERE 条件
func (s *PaymentServiceImpl) buildPaymentWhereClause(filters PaymentFilters) (string, []any) {
var conditions []string
var args []any
if filters.PaymentNo != "" {
conditions = append(conditions, "payment_no LIKE ?")
args = append(args, filters.PaymentNo+"%")
}
if filters.OrderNo != "" {
conditions = append(conditions, "order_no LIKE ?")
args = append(args, filters.OrderNo+"%")
}
if filters.PaymentMethodID > 0 {
conditions = append(conditions, "payment_method_id = ?")
args = append(args, filters.PaymentMethodID)
}
if filters.UserID > 0 {
conditions = append(conditions, "user_id = ?")
args = append(args, filters.UserID)
}
if filters.Status != "" {
conditions = append(conditions, "status = ?")
args = append(args, filters.Status)
}
if !filters.StartTime.IsZero() {
conditions = append(conditions, "created_at >= ?")
args = append(args, filters.StartTime)
}
if !filters.EndTime.IsZero() {
conditions = append(conditions, "created_at <= ?")
args = append(args, filters.EndTime)
}
if len(conditions) == 0 {
return "", nil
}
return strings.Join(conditions, " AND "), args
}
// applyOrderBy 应用排序
func (s *PaymentServiceImpl) applyOrderBy(query orm.Query, orderBy string) orm.Query {
// 解析排序字段,格式:字段:asc/desc
parts := strings.Split(orderBy, ":")
if len(parts) != 2 {
// 默认排序
return query.Order("created_at desc")
}
field := parts[0]
direction := strings.ToLower(parts[1])
// 允许排序的字段
allowedFields := map[string]bool{
"id": true,
"name": true,
"code": true,
"type": true,
"is_active": true,
"sort": true,
"created_at": true,
"updated_at": true,
}
if !allowedFields[field] {
// 如果字段不允许,使用默认排序
return query.Order("created_at desc")
}
if direction == "asc" {
return query.Order(field + " asc")
} else {
return query.Order(field + " desc")
}
}
// getPaymentTableColumns 获取支付记录表的所有列名(用于 UNION ALL 查询)
func (s *PaymentServiceImpl) getPaymentTableColumns() string {
columns := []string{
"id",
"payment_no",
"order_no",
"payment_method_id",
"user_id",
"amount",
"status",
"third_party_no",
"pay_time",
"fail_reason",
"notify_data",
"remark",
"created_at",
"updated_at",
"deleted_at",
}
return strings.Join(columns, ", ")
}
// buildPaymentShardingWhereClause 构建支付记录分表查询的 WHERE 条件(用于通用分表查询服务)
func (s *PaymentServiceImpl) buildPaymentShardingWhereClause(filters any) (string, []any) {
paymentFilters, ok := filters.(PaymentFilters)
if !ok {
return "", nil
}
var conditions []string
var args []any
// 时间范围(必填)
if !paymentFilters.StartTime.IsZero() {
conditions = append(conditions, "created_at >= ?")
args = append(args, paymentFilters.StartTime)
}
if !paymentFilters.EndTime.IsZero() {
conditions = append(conditions, "created_at <= ?")
args = append(args, paymentFilters.EndTime)
}
// 支付单号筛选
if paymentFilters.PaymentNo != "" {
conditions = append(conditions, "payment_no = ?")
args = append(args, paymentFilters.PaymentNo)
}
// 订单号筛选
if paymentFilters.OrderNo != "" {
conditions = append(conditions, "order_no = ?")
args = append(args, paymentFilters.OrderNo)
}
// 支付方式ID筛选
if paymentFilters.PaymentMethodID > 0 {
conditions = append(conditions, "payment_method_id = ?")
args = append(args, paymentFilters.PaymentMethodID)
}
// 用户ID筛选
if paymentFilters.UserID > 0 {
conditions = append(conditions, "user_id = ?")
args = append(args, paymentFilters.UserID)
}
// 状态筛选
if paymentFilters.Status != "" {
conditions = append(conditions, "status = ?")
args = append(args, paymentFilters.Status)
}
return strings.Join(conditions, " AND "), args
}
// findPaymentByPaymentNo 通过支付单号查找支付记录(直接定位分表)
// 支付单号格式:PAY + YYYYMMDD + ULID,可以从中提取日期
func (s *PaymentServiceImpl) findPaymentByPaymentNo(paymentNo string) (*models.Payment, error) {
// 从支付单号中解析日期
if parsedTime, ok := utils.ParseShardingNoDate(paymentNo, utils.PaymentNoConfig); ok {
// 成功解析日期,直接查询对应分表
tableName := utils.GetShardingTableName("payments", parsedTime)
if facades.Schema().HasTable(tableName) {
var payment models.Payment
if err := facades.Orm().Query().Model(&models.Payment{}).Table(tableName).Where("payment_no", paymentNo).First(&payment); err == nil {
return &payment, nil
}
}
}
// 如果无法从支付单号解析日期,或者在对应分表中找不到,遍历最近6个月的分表
now := time.Now().UTC()
startTime := now.AddDate(0, -6, 0)
tableNames := utils.GetShardingTableNames("payments", startTime, now)
// 从最新的分表开始查询(Model 自动应用软删除过滤)
for i := len(tableNames) - 1; i >= 0; i-- {
if !facades.Schema().HasTable(tableNames[i]) {
continue
}
var payment models.Payment
if err := facades.Orm().Query().Model(&models.Payment{}).Table(tableNames[i]).Where("payment_no", paymentNo).First(&payment); err == nil {
return &payment, nil
}
}
return nil, apperrors.ErrPaymentNotFound
}
// findPaymentByID 通过支付记录ID查找支付记录
func (s *PaymentServiceImpl) findPaymentByID(paymentID uint, paymentNo ...string) (*models.Payment, error) {
// 如果提供了支付单号,优先使用支付单号直接定位分表(更高效)
if len(paymentNo) > 0 && paymentNo[0] != "" {
payment, err := s.findPaymentByPaymentNo(paymentNo[0])
if err == nil {
if paymentID > 0 && payment.ID != paymentID {
// 支付单号找到了但ID不匹配,继续用ID查找
} else {
return payment, nil
}
}
}
// 如果没有支付单号或通过支付单号查找失败,使用ID遍历分表
if paymentID == 0 {
return nil, apperrors.ErrPaymentNotFound
}
// 查询最近6个月的分表(足够覆盖大部分场景)
now := time.Now().UTC()
startTime := now.AddDate(0, -6, 0)
tableNames := utils.GetShardingTableNames("payments", startTime, now)
// 从最新的分表开始查询(Model 自动应用软删除过滤)
for i := len(tableNames) - 1; i >= 0; i-- {
if !facades.Schema().HasTable(tableNames[i]) {
continue
}
var payment models.Payment
if err := facades.Orm().Query().Model(&models.Payment{}).Table(tableNames[i]).Where("id", paymentID).First(&payment); err == nil {
return &payment, nil
}
}
return nil, apperrors.ErrPaymentNotFound
}
// querySinglePaymentTable 查询单个分表
func (s *PaymentServiceImpl) querySinglePaymentTable(tableName string, filters PaymentFilters, page, pageSize int) ([]models.Payment, int64, error) {
// 应用排序
orderBy := filters.OrderBy
if orderBy == "" {
orderBy = "created_at:desc"
}
// 构建基础查询条件
query := s.buildPaymentShardingQuery(tableName, filters)
query = s.applyOrderBy(query, orderBy)
// 获取总数(使用 CountOptimizer 优化,超过阈值使用 EXPLAIN 估算)
whereClause, whereArgs := s.buildPaymentShardingWhereClause(filters)
countOptimizer := utils.NewCountOptimizer(PaymentCountThreshold, "payment")
total, _, err := countOptimizer.OptimizedCountWithTable(tableName, whereClause, whereArgs...)
if err != nil {
return nil, 0, err
}
// 执行分页查询
var payments []models.Payment
if err := query.Offset((page - 1) * pageSize).Limit(pageSize).Find(&payments); err != nil {
return nil, 0, err
}
return payments, total, nil
}
// buildPaymentShardingQuery 构建分表查询条件
func (s *PaymentServiceImpl) buildPaymentShardingQuery(tableName string, filters PaymentFilters) orm.Query {
query := facades.Orm().Query().Table(tableName)
// 时间范围
if !filters.StartTime.IsZero() {
query = query.Where("created_at >= ?", filters.StartTime)
}
if !filters.EndTime.IsZero() {
query = query.Where("created_at <= ?", filters.EndTime)
}
// 支付单号筛选
if filters.PaymentNo != "" {
query = query.Where("payment_no = ?", filters.PaymentNo)
}
// 订单号筛选
if filters.OrderNo != "" {
query = query.Where("order_no = ?", filters.OrderNo)
}
// 支付方式ID筛选
if filters.PaymentMethodID > 0 {
query = query.Where("payment_method_id = ?", filters.PaymentMethodID)
}
// 用户ID筛选
if filters.UserID > 0 {
query = query.Where("user_id = ?", filters.UserID)
}
// 状态筛选
if filters.Status != "" {
query = query.Where("status = ?", filters.Status)
}
return query
}
// queryMultiplePaymentTablesWithUnion 使用 UNION ALL 查询多个分表
func (s *PaymentServiceImpl) queryMultiplePaymentTablesWithUnion(tableNames []string, filters PaymentFilters, page, pageSize int) ([]models.Payment, int64, error) {
var payments []models.Payment
total, err := s.shardingQueryService.QueryMultipleTables(tableNames, filters, page, pageSize, &payments)
if err != nil {
return nil, 0, err
}
return payments, total, nil
}
// ensurePaymentShardingTableExists 确保支付记录分表存在
func (s *PaymentServiceImpl) ensurePaymentShardingTableExists(paymentTime time.Time) (string, error) {
tableName := utils.GetShardingTableName("payments", paymentTime)
// 检查分表是否存在
if !facades.Schema().HasTable(tableName) {
// 使用迁移函数创建分表
if err := migrations.CreatePaymentsShardingTable(tableName); err != nil {
errorlog.Record(context.Background(), "payment", "创建支付记录分表失败", map[string]any{
"table_name": tableName,
"error": err.Error(),
}, "创建支付记录分表失败: %v", err)
return "", fmt.Errorf("创建支付记录分表失败: %v", err)
}
}
return tableName, nil
}