feat: 初始提交云印享付费打印服务后端
This commit is contained in:
201
cloudprint-backend/internal/grpc/service/pay_service.go
Normal file
201
cloudprint-backend/internal/grpc/service/pay_service.go
Normal file
@@ -0,0 +1,201 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user