package service import ( "context" "github.com/cloudprint/cloudprint-backend/internal/model" "github.com/cloudprint/cloudprint-backend/internal/repository" "github.com/cloudprint/cloudprint-backend/internal/service" "github.com/cloudprint/cloudprint-backend/pkg/pay" pb "github.com/cloudprint/cloudprint-backend/proto/generated" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type PayServiceServer struct { pb.UnimplementedPayServiceServer orderService *service.OrderService paymentRepo *repository.PaymentRepository weChatPay *pay.WeChatPay userRepo *repository.UserRepository } func NewPayServiceServer( orderService *service.OrderService, paymentRepo *repository.PaymentRepository, weChatPay *pay.WeChatPay, userRepo *repository.UserRepository, ) *PayServiceServer { return &PayServiceServer{ orderService: orderService, paymentRepo: paymentRepo, weChatPay: weChatPay, userRepo: userRepo, } } func (s *PayServiceServer) CreateWxPayOrder(ctx context.Context, req *pb.WxPayRequest) (*pb.WxPayResponse, error) { resp, err := s.weChatPay.UnifiedOrder(ctx, req.OrderNo, req.Amount, req.Description, req.Openid, req.ClientIp) if err != nil { return nil, status.Errorf(codes.Internal, "create pay order failed: %v", err) } if resp.ReturnCode != "SUCCESS" { return nil, status.Errorf(codes.Internal, "wechat pay error: %s - %s", resp.ErrCode, resp.ErrMsg) } // Create payment record payment := &model.Payment{ OrderNo: req.OrderNo, PayType: "wechat", PayStatus: model.PayStatusPaying, } if err := s.paymentRepo.Create(ctx, payment); err != nil { return nil, status.Errorf(codes.Internal, "create payment record failed: %v", err) } return &pb.WxPayResponse{ PrepayId: resp.PrepayID, CodeUrl: resp.CodeURL, MwebUrl: resp.MWebURL, }, nil } func (s *PayServiceServer) QueryPayStatus(ctx context.Context, req *pb.QueryPayStatusRequest) (*pb.PayStatusResponse, error) { tradeState, _, err := s.weChatPay.QueryOrder(ctx, req.OrderNo) if err != nil { return nil, status.Errorf(codes.Internal, "query pay status failed: %v", err) } var payStatus int32 switch tradeState { case "SUCCESS": payStatus = int32(model.PayStatusSuccess) case "USERPAYING", "PAYING": payStatus = int32(model.PayStatusPaying) case "CLOSED", "PAYERROR", "REFUND": payStatus = int32(model.PayStatusFailed) default: payStatus = int32(model.PayStatusPending) } return &pb.PayStatusResponse{ Status: payStatus, }, nil } func (s *PayServiceServer) HandlePayCallback(ctx context.Context, req *pb.PayCallbackRequest) (*pb.PayCallbackResponse, error) { callback, err := s.weChatPay.ParseCallback([]byte(req.Body)) if err != nil { return &pb.PayCallbackResponse{ ReturnCode: "FAIL", ReturnMsg: "parse error", }, nil } if !s.weChatPay.ValidateCallback(callback) { return &pb.PayCallbackResponse{ ReturnCode: "FAIL", ReturnMsg: "validate error", }, nil } if callback.ReturnCode == "SUCCESS" && callback.ResultCode == "SUCCESS" { // Check if payment already processed (idempotency) payment, _ := s.paymentRepo.GetByOrderNo(ctx, callback.OutTradeNo) if payment != nil && payment.PayStatus == model.PayStatusSuccess { // Already processed, return success return &pb.PayCallbackResponse{ ReturnCode: "SUCCESS", ReturnMsg: "OK", }, nil } // Update payment status if err := s.paymentRepo.UpdateStatus(ctx, callback.OutTradeNo, model.PayStatusSuccess, callback.TransactionID); err != nil { return &pb.PayCallbackResponse{ ReturnCode: "FAIL", ReturnMsg: "update payment failed", }, nil } // Update order status - pay order triggers print job creation if err := s.orderService.PayOrder(ctx, callback.OutTradeNo, "wechat"); err != nil { return &pb.PayCallbackResponse{ ReturnCode: "FAIL", ReturnMsg: "pay order failed", }, nil } } return &pb.PayCallbackResponse{ ReturnCode: "SUCCESS", ReturnMsg: "OK", }, nil } func (s *PayServiceServer) PayByBalance(ctx context.Context, req *pb.PayByBalanceRequest) (*pb.StatusResponse, error) { // Get order order, err := s.orderService.GetOrder(ctx, req.OrderNo) if err != nil || order == nil { return &pb.StatusResponse{ Code: 1, Message: "order not found", }, nil } // Check if order is pending if order.Status != model.OrderStatusPending { return &pb.StatusResponse{ Code: 1, Message: "order is not pending", }, nil } // Get user user, err := s.userRepo.GetByID(ctx, req.UserId) if err != nil || user == nil { return &pb.StatusResponse{ Code: 1, Message: "user not found", }, nil } // Check balance if user.Balance < order.TotalAmount { return &pb.StatusResponse{ Code: 1, Message: "insufficient balance", }, nil } // Deduct balance if err := s.userRepo.DeductBalance(ctx, req.UserId, order.TotalAmount); err != nil { return nil, status.Errorf(codes.Internal, "deduct balance failed: %v", err) } // Create payment record payment := &model.Payment{ OrderNo: order.OrderNo, PayType: "balance", PayStatus: model.PayStatusSuccess, TransactionID: "balance_" + order.OrderNo, } if err := s.paymentRepo.Create(ctx, payment); err != nil { // Rollback balance deduction on failure s.userRepo.AddBalance(ctx, req.UserId, order.TotalAmount) return nil, status.Errorf(codes.Internal, "create payment record failed: %v", err) } // Pay order - this creates print jobs if err := s.orderService.PayOrder(ctx, req.OrderNo, "balance"); err != nil { // Rollback s.userRepo.AddBalance(ctx, req.UserId, order.TotalAmount) return nil, status.Errorf(codes.Internal, "pay order failed: %v", err) } return &pb.StatusResponse{ Code: 0, Message: "success", }, nil }