package service import ( "context" "errors" "fmt" "math/rand" "time" "order/consts" "order/dao" "order/model/dto" "order/model/entity" "gitee.com/red-future---jilin-g/common/http" "github.com/gogf/gf/v2/frame/g" "go.mongodb.org/mongo-driver/v2/bson" ) type payment struct{} // Payment 支付服务 var Payment = new(payment) // PayOrder 支付订单 func (s *payment) PayOrder(ctx context.Context, req *dto.PayOrderReq) (*dto.PayOrderResp, error) { // 1. 参数验证 if req.TenantID == "" || req.OrderNo == "" || req.PayMethod == "" || req.PayType == "" { return nil, errors.New("必填参数不能为空") } // 2. 查询订单 order, status, err := dao.Order.GetOrderByNo(ctx, req.OrderNo) if err != nil { return nil, fmt.Errorf("获取订单失败: %w", err) } if order == nil { return nil, errors.New("订单不存在") } // 3. 验证订单状态(只有待支付订单可以支付) if status != consts.OrderStatusPending { return nil, fmt.Errorf("订单状态不正确,当前状态: %s", status) } pendingOrder, ok := order.(*entity.OrderPending) if !ok { return nil, errors.New("订单类型错误") } // 4. 获取支付配置 paymentConfig, err := dao.PaymentConfig.GetByTenantAndMethod(ctx, req.TenantID, req.PayMethod) if err != nil { return nil, fmt.Errorf("获取支付配置失败: %w", err) } if paymentConfig == nil { return nil, errors.New("支付配置不存在") } // 5. 调用第三方支付接口 payResp, err := s.callThirdPartyPayment(ctx, paymentConfig, req, pendingOrder) if err != nil { return nil, fmt.Errorf("调用支付接口失败: %w", err) } // 6. 创建支付记录 paymentRecord := &entity.PaymentRecord{ OrderID: pendingOrder.Id, OrderNo: req.OrderNo, PayMethod: req.PayMethod, PayType: req.PayType, Amount: pendingOrder.PayAmount, OutTradeNo: payResp.OutTradeNo, Status: "pending", } if err := dao.PaymentRecord.Create(ctx, paymentRecord); err != nil { return nil, fmt.Errorf("创建支付记录失败: %w", err) } // 7. 更新订单支付信息 payInfo := entity.PayInfo{ OutTradeNo: payResp.OutTradeNo, QRCode: payResp.QRCode, PayURL: payResp.PayURL, PrepayID: payResp.PrepayID, JSAPIParams: payResp.JSAPIParams, APPParams: payResp.APPParams, } if err := dao.Order.UpdatePayInfo(ctx, req.OrderNo, payInfo); err != nil { return nil, fmt.Errorf("更新订单支付信息失败: %w", err) } // 8. 返回支付信息 resp := &dto.PayOrderResp{ OrderNo: req.OrderNo, QRCode: payResp.QRCode, PayURL: payResp.PayURL, PrepayID: payResp.PrepayID, JSAPIParams: payResp.JSAPIParams, APPParams: payResp.APPParams, } return resp, nil } // callThirdPartyPayment 调用第三方支付接口 func (s *payment) callThirdPartyPayment(ctx context.Context, config *entity.PaymentConfig, req *dto.PayOrderReq, order *entity.OrderPending) (*PaymentResponse, error) { // 这里应该是实际的第三方支付接口调用 // 为了演示,我们返回模拟数据 outTradeNo := s.generateOutTradeNo(req.TenantID) payResp := &PaymentResponse{ OutTradeNo: outTradeNo, } // 根据支付类型返回不同的支付参数 switch req.PayType { case "native": // 扫码支付 payResp.QRCode = fmt.Sprintf("https://api.example.com/qrcode/%s", outTradeNo) case "jsapi": // JSAPI支付 payResp.PrepayID = fmt.Sprintf("wxprepay_%s", outTradeNo) payResp.JSAPIParams = fmt.Sprintf(`{"appId":"%s","timeStamp":"%d","nonceStr":"%s","package":"prepay_id=%s","signType":"MD5","paySign":"signature"}`, config.AppID, time.Now().Unix(), s.generateNonceStr(), payResp.PrepayID) case "app": // APP支付 payResp.APPParams = fmt.Sprintf(`{"appid":"%s","partnerid":"%s","prepayid":"%s","package":"Sign=WXPay","noncestr":"%s","timestamp":"%d","sign":"signature"}`, config.AppID, config.MchID, payResp.PrepayID, s.generateNonceStr(), time.Now().Unix()) case "h5": // H5支付 payResp.PayURL = fmt.Sprintf("https://api.example.com/h5pay/%s", outTradeNo) } return payResp, nil } // PaymentResponse 支付响应 type PaymentResponse struct { OutTradeNo string `json:"out_trade_no"` // 商户订单号 QRCode string `json:"qrcode"` // 支付二维码 PayURL string `json:"pay_url"` // 支付链接 PrepayID string `json:"prepay_id"` // 预支付ID JSAPIParams string `json:"jsapi_params"` // JSAPI参数 APPParams string `json:"app_params"` // APP参数 } // generateOutTradeNo 生成商户订单号 func (s *payment) generateOutTradeNo(tenantID string) string { timestamp := time.Now().Format("20060102150405") random := rand.Intn(10000) return fmt.Sprintf("%s%s%04d", tenantID, timestamp, random) } // generateNonceStr 生成随机字符串 func (s *payment) generateNonceStr() string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, 16) for i := range b { b[i] = charset[rand.Intn(len(charset))] } return string(b) } // HandlePaymentNotify 处理支付回调 func (s *payment) HandlePaymentNotify(ctx context.Context, req *dto.PaymentNotifyReq) error { // 1. 验证回调签名 if !s.verifyNotifySignature(req) { return errors.New("签名验证失败") } // 2. 查询支付记录 paymentRecord, err := dao.PaymentRecord.GetByOrderNo(ctx, req.TenantID, req.OrderNo) if err != nil { return fmt.Errorf("查询支付记录失败: %w", err) } if paymentRecord == nil { return errors.New("支付记录不存在") } // 3. 更新支付记录状态 if err := dao.PaymentRecord.UpdateStatus(ctx, paymentRecord.Id.Hex(), req.Status, req.TransactionID, req.TradeNo); err != nil { return fmt.Errorf("更新支付记录失败: %w", err) } // 4. 如果支付成功,更新订单状态并处理库存 if req.Status == "success" { // 获取订单信息以处理库存 order, err := dao.Order.GetPendingOrder(ctx, req.OrderNo) if err != nil { return fmt.Errorf("获取待支付订单失败: %w", err) } if order == nil { return errors.New("待支付订单不存在") } // 处理库存扣减 for _, item := range order.OrderItems { // 判断是否需要扣减库存 shouldDeduct, saleMode, err := s.shouldDeductStock(ctx, paymentRecord.TenantId.(string), item.AssetID) if err != nil { g.Log().Errorf(ctx, "检查库存策略失败,订单:%s,资产:%s,错误:%v", req.OrderNo, item.AssetID, err) continue } // 如果需要扣减库存,判断扣减时机 if shouldDeduct { timing, err := s.getDeductStockTiming(ctx, paymentRecord.TenantId.(string), item.AssetID) if err != nil { g.Log().Errorf(ctx, "获取库存扣减时机失败,订单:%s,资产:%s,错误:%v", req.OrderNo, item.AssetID, err) continue } // 常规售卖:支付成功时扣减库存 if timing == "payment_success" { quantity := len(item.Stocks) // 每个库存项数量为1 if err := s.deductStock(ctx, paymentRecord.TenantId.(string), req.OrderNo, item.AssetID, quantity, fmt.Sprintf("支付成功扣减库存,售卖方式:%s", saleMode)); err != nil { g.Log().Errorf(ctx, "支付成功扣减库存失败,订单:%s,资产:%s,错误:%v", req.OrderNo, item.AssetID, err) // 库存扣减失败不影响订单状态更新,但需要记录错误 continue } g.Log().Infof(ctx, "资产 %s 支付成功扣减库存成功,订单:%s,数量:%d", item.AssetID, req.OrderNo, quantity) } } } // 更新订单状态 updateData := bson.M{ "paid_at": time.Now(), "transaction_id": req.TransactionID, "trade_no": req.TradeNo, "payment_channel": req.PayMethod, } if err := dao.Order.MoveOrderToStatus(ctx, consts.OrderStatusPending, consts.OrderStatusPaid, req.OrderNo, updateData); err != nil { return fmt.Errorf("更新订单状态失败: %w", err) } } return nil } // verifyNotifySignature 验证回调签名 func (s *payment) verifyNotifySignature(req *dto.PaymentNotifyReq) bool { // 这里应该是实际的签名验证逻辑 // 为了演示,我们总是返回true return true } // RefundOrder 退款 func (s *payment) RefundOrder(ctx context.Context, req *dto.RefundOrderReq) (*dto.RefundOrderResp, error) { // 1. 参数验证 if req.TenantID == "" || req.OrderNo == "" || req.RefundAmount <= 0 { return nil, errors.New("必填参数不能为空") } // 2. 查询订单 order, status, err := dao.Order.GetOrderByNo(ctx, req.OrderNo) if err != nil { return nil, fmt.Errorf("获取订单失败: %w", err) } if order == nil { return nil, errors.New("订单不存在") } // 3. 验证订单状态(只有已支付订单可以退款) if status != consts.OrderStatusPaid { return nil, fmt.Errorf("订单状态不正确,当前状态: %s", status) } paidOrder, ok := order.(*entity.OrderPaid) if !ok { return nil, errors.New("订单类型错误") } // 4. 验证退款金额 if req.RefundAmount > paidOrder.PayAmount { return nil, errors.New("退款金额不能超过支付金额") } // 5. 调用第三方退款接口 refundResp, err := s.callThirdPartyRefund(ctx, req, paidOrder) if err != nil { return nil, fmt.Errorf("调用退款接口失败: %w", err) } // 6. 创建退款记录 refundRecord := &entity.RefundRecord{ OrderID: paidOrder.Id, OrderNo: req.OrderNo, RefundNo: refundResp.RefundNo, RefundAmount: req.RefundAmount, Reason: req.Reason, Status: "pending", } if err := dao.RefundRecord.Create(ctx, refundRecord); err != nil { return nil, fmt.Errorf("创建退款记录失败: %w", err) } // 7. 如果是全额退款,更新订单状态 if req.RefundAmount == paidOrder.PayAmount { updateData := bson.M{ "refund_reason": req.Reason, } if err := dao.Order.MoveOrderToStatus(ctx, consts.OrderStatusPaid, consts.OrderStatusRefunded, req.OrderNo, updateData); err != nil { return nil, fmt.Errorf("更新订单状态失败: %w", err) } } // 8. 返回退款结果 resp := &dto.RefundOrderResp{ RefundNo: refundResp.RefundNo, RefundID: refundResp.RefundID, RefundAmount: req.RefundAmount, } return resp, nil } // callThirdPartyRefund 调用第三方退款接口 func (s *payment) callThirdPartyRefund(ctx context.Context, req *dto.RefundOrderReq, order *entity.OrderPaid) (*RefundResponse, error) { // 这里应该是实际的第三方退款接口调用 // 为了演示,我们返回模拟数据 refundNo := s.generateRefundNo(req.TenantID) refundID := fmt.Sprintf("refund_%s", refundNo) return &RefundResponse{ RefundNo: refundNo, RefundID: refundID, }, nil } // RefundResponse 退款响应 type RefundResponse struct { RefundNo string `json:"refund_no"` // 退款单号 RefundID string `json:"refund_id"` // 退款ID } // generateRefundNo 生成退款单号 func (s *payment) generateRefundNo(tenantID string) string { timestamp := time.Now().Format("20060102150405") random := rand.Intn(10000) return fmt.Sprintf("R%s%s%04d", tenantID, timestamp, random) } // HandleRefundNotify 处理退款回调 func (s *payment) HandleRefundNotify(ctx context.Context, req *dto.RefundNotifyReq) error { // 1. 验证回调签名 if !s.verifyRefundNotifySignature(req) { return errors.New("签名验证失败") } // 2. 查询退款记录 refundRecord, err := dao.RefundRecord.GetByRefundNo(ctx, req.TenantID, req.RefundNo) if err != nil { return fmt.Errorf("查询退款记录失败: %w", err) } if refundRecord == nil { return errors.New("退款记录不存在") } // 3. 更新退款记录状态 if err := dao.RefundRecord.UpdateRefundStatus(ctx, refundRecord.Id.Hex(), req.Status, req.RefundID); err != nil { return fmt.Errorf("更新退款记录失败: %w", err) } return nil } // verifyRefundNotifySignature 验证退款回调签名 func (s *payment) verifyRefundNotifySignature(req *dto.RefundNotifyReq) bool { // 这里应该是实际的签名验证逻辑 // 为了演示,我们总是返回true return true } // shouldDeductStock 判断是否需要扣减库存 func (s *payment) shouldDeductStock(ctx context.Context, tenantID, assetID string) (bool, string, error) { // 调用assets服务获取资产信息 assetResp, err := s.getAssetFromAssetService(ctx, tenantID, assetID) if err != nil { return false, "", fmt.Errorf("获取资产信息失败: %w", err) } if assetResp == nil { return false, "", errors.New("资产不存在") } // 无库存限制的资产不需要扣减库存 if assetResp.UnlimitedStock { return false, "unlimited_stock", nil } // 预售场景不扣减库存 if assetResp.SaleMode == "presale" { return false, "presale_no_deduct", nil } // 有库存限制的常规售卖和秒杀需要扣减库存 return true, assetResp.SaleMode, nil } // getDeductStockTiming 获取库存扣减时机 func (s *payment) getDeductStockTiming(ctx context.Context, tenantID, assetID string) (string, error) { assetResp, err := s.getAssetFromAssetService(ctx, tenantID, assetID) if err != nil { return "", fmt.Errorf("获取资产信息失败: %w", err) } if assetResp == nil { return "", errors.New("资产不存在") } // 根据售卖方式确定扣减时机 switch assetResp.SaleMode { case "flash": return "order_create", nil // 秒杀:下单时扣减库存 case "regular": return "payment_success", nil // 常规售卖:支付成功时扣减库存 default: return "", errors.New("未知售卖方式") } } // deductStock 扣减库存 func (s *payment) deductStock(ctx context.Context, tenantID, orderNo, assetID string, quantity int, reason string) error { // 调用assets服务扣减库存 err := s.deductStockFromAssetService(ctx, tenantID, assetID, quantity) if err != nil { return fmt.Errorf("扣减库存失败: %w", err) } g.Log().Infof(ctx, "库存扣减成功,订单:%s,资产:%s,数量:%d,原因:%s", orderNo, assetID, quantity, reason) return nil } // refundStock 回充库存 func (s *payment) refundStock(ctx context.Context, tenantID, orderNo, assetID string, quantity int, reason string) error { // 调用资产服务回充库存 err := s.refundStockFromAssetService(ctx, tenantID, assetID, quantity) if err != nil { return fmt.Errorf("回充库存失败: %w", err) } g.Log().Infof(ctx, "库存回充成功,订单:%s,资产:%s,数量:%d,原因:%s", orderNo, assetID, quantity, reason) return nil } // getAssetFromAssetService 从资产服务获取资产信息 func (s *payment) getAssetFromAssetService(ctx context.Context, tenantID, assetID string) (*AssetServiceResponse, error) { // 使用common/http中的封装方法调用资产服务 // 这里应该调用assets服务的getAsset接口 // 暂时返回模拟数据 var assetResp AssetServiceResponse err := http.Get(ctx, fmt.Sprintf("http://assets-service/internal/asset/%s", assetID), nil, &assetResp) if err != nil { return nil, err } return &assetResp, nil } // deductStockFromAssetService 从资产服务扣减库存 func (s *payment) deductStockFromAssetService(ctx context.Context, tenantID, assetID string, quantity int) error { // 调用assets服务的扣减库存接口 req := map[string]interface{}{ "asset_id": assetID, "quantity": quantity, "reason": "订单扣减", } var result struct { Success bool `json:"success"` Message string `json:"message"` } err := http.Post(ctx, "http://assets-service/internal/stock/deduct", nil, &result, req) if err != nil { return err } if !result.Success { return errors.New(result.Message) } return nil } // refundStockFromAssetService 从资产服务回充库存 func (s *payment) refundStockFromAssetService(ctx context.Context, tenantID, assetID string, quantity int) error { // 调用资产服务的回充库存接口 req := map[string]interface{}{ "asset_id": assetID, "quantity": quantity, "reason": "订单取消回充", } var result struct { Success bool `json:"success"` Message string `json:"message"` } err := http.Post(ctx, "http://assets-service/internal/stock/refund", nil, &result, req) if err != nil { return err } if !result.Success { return errors.New(result.Message) } return nil }