202 lines
5.6 KiB
Go
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
|
|
}
|