Files
backend/cloudprint-backend/internal/grpc/service/pay_service.go
2026-03-29 14:29:43 +08:00

202 lines
5.6 KiB
Go

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(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(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
}