tyc-server/app/main/api/internal/service/payService.go
2025-06-03 20:59:26 +08:00

497 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"database/sql"
"fmt"
"net/http"
"time"
"tyc-server/app/main/api/internal/config"
"tyc-server/app/main/model"
"tyc-server/common/ctxdata"
"tyc-server/pkg/core/payment"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
// PayService 支付服务
type PayService struct {
config config.Config
orderModel model.OrderModel
orderRefundModel model.OrderRefundModel
productModel model.ProductModel
userAuthModel model.UserAuthModel
userModel model.UserModel
alipayService *payment.AliPayService
wechatService *payment.WechatPayService
appleService *payment.ApplePayService
}
// NewPayService 创建支付服务
func NewPayService(c config.Config, orderModel model.OrderModel, orderRefundModel model.OrderRefundModel, productModel model.ProductModel, userAuthModel model.UserAuthModel, userModel model.UserModel) *PayService {
ps := &PayService{
config: c,
orderModel: orderModel,
orderRefundModel: orderRefundModel,
productModel: productModel,
userAuthModel: userAuthModel,
userModel: userModel,
}
// 根据配置选择性初始化支付服务
if c.Alipay.Enabled {
// 初始化支付宝服务
alipayConfig := payment.AlipayConfig{
AppID: c.Alipay.AppID,
PrivateKey: c.Alipay.PrivateKey,
AlipayPublicKey: c.Alipay.AlipayPublicKey,
IsProduction: c.Alipay.IsProduction,
NotifyURL: c.Alipay.NotifyUrl,
ReturnURL: c.Alipay.ReturnURL,
}
alipayService, err := payment.NewAliPayService(alipayConfig)
if err != nil {
panic(fmt.Sprintf("初始化支付宝服务失败: %v", err))
}
ps.alipayService = alipayService
}
if c.Wxpay.Enabled {
// 初始化微信支付服务
wechatConfig := &payment.WechatPayConfig{
AppID: c.Wxpay.AppID,
MchID: c.Wxpay.MchID,
SerialNo: c.Wxpay.MchCertificateSerialNumber,
MchAPIv3Key: c.Wxpay.MchApiv3Key,
PrivateKey: c.Wxpay.MchPrivateKeyPath,
NotifyURL: c.Wxpay.NotifyUrl,
RefundURL: c.Wxpay.RefundNotifyUrl,
}
wechatService, err := payment.NewWechatPayService(wechatConfig)
if err != nil {
panic(fmt.Sprintf("初始化微信支付服务失败: %v", err))
}
ps.wechatService = wechatService
}
if c.Applepay.Enabled {
// 初始化苹果支付服务
appleConfig := payment.ApplePayConfig{
BundleID: c.Applepay.BundleID,
KeyID: c.Applepay.KeyID,
TeamID: c.Applepay.IssuerID,
PrivateKey: c.Applepay.LoadPrivateKeyPath,
IsProduction: !c.Applepay.Sandbox,
NotifyURL: c.Applepay.ProductionVerifyURL,
RefundNotifyURL: c.Applepay.SandboxVerifyURL,
}
appleService, err := payment.NewApplePayService(appleConfig)
if err != nil {
panic(fmt.Sprintf("初始化苹果支付服务失败: %v", err))
}
ps.appleService = appleService
}
return ps
}
// CreateOrder 创建支付订单
func (p *PayService) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*CreateOrderResponse, error) {
platform, ok := ctx.Value("platform").(string)
if !ok {
return nil, fmt.Errorf("获取平台失败")
}
userID, err := ctxdata.GetUidFromCtx(ctx)
if err != nil {
return nil, fmt.Errorf("获取用户信息失败: %v", err)
}
product, err := p.productModel.FindOneByProductEn(ctx, req.ProductEn)
if err != nil {
return nil, fmt.Errorf("获取商品失败: %v", err)
}
user, err := p.userModel.FindOne(ctx, userID)
if err != nil {
return nil, fmt.Errorf("获取用户失败: %v", err)
}
var outTradeNo string
var orderID int64
amount := product.SellPrice
if user.Inside == 1 {
amount = 0.01
}
// 使用事务创建订单
var payData interface{}
err = p.orderModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
// 根据支付方式创建支付订单
switch req.PayMethod {
case "alipay":
orderReq := &payment.AlipayCreateOrderRequest{
Amount: amount,
Subject: product.ProductName,
Platform: payment.PlatformApp,
}
resp, err := p.alipayService.CreateOrder(ctx, orderReq)
if err != nil {
return fmt.Errorf("创建支付宝订单失败: %v", err)
}
payData = resp.PayParams
case "wechat":
orderReq := &payment.Order{
OutTradeNo: outTradeNo,
Subject: product.ProductName,
Amount: amount,
Platform: payment.PlatformApp,
NotifyURL: p.config.Wxpay.NotifyUrl,
}
// 如果是小程序支付,需要获取用户的 OpenID
if platform == "mp-weixin" {
userAuth, err := p.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWechatMiniOpenID)
if err != nil {
return fmt.Errorf("获取用户认证信息失败: %v", err)
}
orderReq.OpenID = userAuth.AuthKey
}
resp, err := p.wechatService.CreateOrder(orderReq)
if err != nil {
return fmt.Errorf("创建微信支付订单失败: %v", err)
}
payData = resp.PayParams
outTradeNo = resp.OrderNo
// case "apple":
// orderReq := &payment.AppleCreateOrderRequest{
// Amount: amount,
// Subject: product.ProductName,
// OutTradeNo: outTradeNo,
// Platform: payment.PlatformApp,
// NotifyURL: p.config.Applepay.ProductionVerifyURL,
// }
// resp, err := p.appleService.CreateOrder(ctx, orderReq)
// if err != nil {
// return fmt.Errorf("创建苹果支付订单失败: %v", err)
// }
// payData = resp.PayParams
default:
return fmt.Errorf("不支持的支付方式: %s", req.PayMethod)
}
// 创建订单记录
order := &model.Order{
OrderNo: outTradeNo,
UserId: userID,
ProductId: product.Id,
PaymentPlatform: string(req.PayMethod),
PaymentScene: platform,
Amount: amount,
Status: model.OrderStatusPending,
}
result, err := p.orderModel.Insert(ctx, session, order)
if err != nil {
return fmt.Errorf("创建订单记录失败: %v", err)
}
orderID, err = result.LastInsertId()
if err != nil {
return fmt.Errorf("获取订单ID失败: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
return &CreateOrderResponse{
OrderNo: outTradeNo,
PayData: payData,
PayMethod: req.PayMethod,
OrderID: orderID,
}, nil
}
// HandlePaymentCallback 处理支付回调
func (p *PayService) HandlePaymentCallback(ctx context.Context, payType string, req *http.Request) error {
return p.orderModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
var payOrder *payment.QueryOrderResponse
var err error
switch payType {
case "alipay":
payOrder, err = p.handleAlipayCallback(ctx, req, session)
case "wechat":
payOrder, err = p.handleWechatCallback(ctx, req, session)
case "apple":
payOrder, err = p.handleAppleCallback(ctx, req, session)
default:
return fmt.Errorf("不支持的支付类型: %s", payType)
}
if err != nil {
return err
}
if payOrder == nil {
return fmt.Errorf("支付订单信息为空")
}
// 查询订单
order, err := p.orderModel.FindOneByOrderNo(ctx, payOrder.OrderNo)
if err != nil {
return fmt.Errorf("查询订单失败: %v", err)
}
// 验证订单金额
if payOrder.Amount != order.Amount {
return fmt.Errorf("订单金额不匹配")
}
// 根据支付状态更新订单状态
switch payOrder.Status {
case payment.StatusSuccess:
order.Status = model.OrderStatusPaid
case payment.StatusFailed:
order.Status = model.OrderStatusFailed
case payment.StatusClosed:
order.Status = model.OrderStatusClosed
case payment.StatusRefunded:
order.Status = model.OrderStatusRefunding
default:
return fmt.Errorf("不支持的支付状态: %s", payOrder.Status)
}
// 更新订单状态
order.PlatformOrderId = sql.NullString{String: payOrder.TransactionID, Valid: true}
_, err = p.orderModel.Update(ctx, session, order)
if err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
}
// handleAlipayCallback 处理支付宝回调
func (p *PayService) handleAlipayCallback(ctx context.Context, req *http.Request, session sqlx.Session) (*payment.QueryOrderResponse, error) {
return p.alipayService.HandlePaymentNotification(req)
}
// handleWechatCallback 处理微信支付回调
func (p *PayService) handleWechatCallback(ctx context.Context, req *http.Request, session sqlx.Session) (*payment.QueryOrderResponse, error) {
notification, err := p.wechatService.HandlePaymentNotification(req)
if err != nil {
return nil, fmt.Errorf("处理微信支付回调失败: %v", err)
}
// 将微信支付通知转换为统一的查询订单响应格式
return &payment.QueryOrderResponse{
OrderNo: notification.OrderNo,
TransactionID: notification.TransactionID,
Status: notification.Status,
Amount: notification.Amount,
PayTime: notification.PayTime,
PayMethod: payment.MethodWechat,
}, nil
}
// handleAppleCallback 处理苹果支付回调
func (p *PayService) handleAppleCallback(ctx context.Context, req *http.Request, session sqlx.Session) (*payment.QueryOrderResponse, error) {
receipt := req.FormValue("receipt-data")
if receipt == "" {
return nil, fmt.Errorf("无效的苹果支付收据")
}
verifyResp, err := p.appleService.VerifyReceipt(ctx, receipt)
if err != nil {
return nil, fmt.Errorf("验证苹果支付收据失败: %v", err)
}
if verifyResp.Status != payment.StatusSuccess {
return nil, fmt.Errorf("苹果支付验证失败,状态码: %s", verifyResp.Status)
}
// 将苹果支付验证响应转换为统一的查询订单响应格式
return &payment.QueryOrderResponse{
OrderNo: verifyResp.OrderNo,
TransactionID: verifyResp.TransactionID,
Status: verifyResp.Status,
Amount: verifyResp.Amount,
PayTime: time.Now(),
PayMethod: payment.MethodApple,
}, nil
}
// Refund 申请退款
func (p *PayService) Refund(ctx context.Context, req *RefundRequest) (*RefundResponse, error) {
// 查询订单信息
order, err := p.orderModel.FindOne(ctx, req.OrderId)
if err != nil {
return nil, fmt.Errorf("查询订单失败: %v", err)
}
// 验证订单状态
if order.Status != model.OrderStatusPaid {
return nil, fmt.Errorf("订单状态为 %s无法退款", order.Status)
}
if req.RefundAmount > order.Amount {
return nil, fmt.Errorf("退款金额 %.2f 不能大于订单金额 %.2f", req.RefundAmount, order.Amount)
}
var result refundResult
result.status = model.OrderRefundStatusPending
order.Status = model.OrderStatusRefunding
// 使用事务处理退款流程
err = p.orderModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
// 根据支付方式申请退款
switch order.PaymentPlatform {
case string(PayMethodAlipay):
refundReq := &payment.AlipayRefundRequest{
OrderNo: order.OrderNo,
RefundAmount: req.RefundAmount,
}
resp, err := p.alipayService.Refund(ctx, refundReq)
if err != nil {
return fmt.Errorf("支付宝退款申请失败: %v", err)
}
result = refundResult{
refundNo: resp.RefundNo,
status: model.OrderRefundStatusSuccess,
refundTime: resp.RefundTime,
refundAmount: resp.RefundAmount,
platformRefundId: sql.NullString{String: resp.RefundID, Valid: true},
}
order.Status = model.OrderRefundStatusSuccess
default:
return fmt.Errorf("不支持的支付平台退款: %s", order.PaymentPlatform)
}
// 创建退款记录
refund := &model.OrderRefund{
RefundNo: result.refundNo,
PlatformRefundId: result.platformRefundId,
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: result.refundAmount,
RefundReason: sql.NullString{String: req.Reason, Valid: true},
Status: result.status,
RefundTime: sql.NullTime{Time: result.refundTime, Valid: !result.refundTime.IsZero()},
}
if _, err := p.orderRefundModel.Insert(ctx, session, refund); err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
// 更新订单状态
order.Status = model.OrderStatusRefunding
order.RefundTime = sql.NullTime{Time: result.refundTime, Valid: !result.refundTime.IsZero()}
if _, err := p.orderModel.Update(ctx, session, order); err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
return &RefundResponse{
RefundNo: result.refundNo,
Amount: result.refundAmount,
Status: result.status,
}, nil
}
// QueryOrderStatus 查询订单状态
func (p *PayService) QueryOrderStatus(ctx context.Context, orderNo string) (*OrderStatusResponse, error) {
// 查询订单信息
order, err := p.orderModel.FindOneByOrderNo(ctx, orderNo)
if err != nil {
return nil, fmt.Errorf("查询订单失败: %v", err)
}
// 根据支付方式查询支付状态
var payStatus payment.PaymentStatus
switch order.PaymentPlatform {
case "alipay":
queryReq := &payment.QueryOrderRequest{
OrderNo: orderNo,
}
resp, err := p.alipayService.QueryOrder(ctx, queryReq)
if err != nil {
return nil, fmt.Errorf("查询支付宝订单状态失败: %v", err)
}
payStatus = resp.Status
// case "wechat":
// queryReq := &payment.OrderQuery{
// OrderNo: orderNo,
// }
// resp, err := p.wechatService.QueryOrder(queryReq)
// if err != nil {
// return nil, fmt.Errorf("查询微信支付订单状态失败: %v", err)
// }
// payStatus = resp.Status
// case "apple":
// return nil, fmt.Errorf("苹果支付暂不支持主动查询订单状态")
default:
return nil, fmt.Errorf("不支持的支付方式: %s", order.PaymentPlatform)
}
return &OrderStatusResponse{
OrderNo: orderNo,
PayMethod: PayMethod(order.PaymentPlatform),
PayStatus: string(payStatus),
OrderStatus: order.Status,
Amount: order.Amount,
}, nil
}
type PayMethod string
const (
PayMethodAlipay PayMethod = "alipay"
PayMethodWechat PayMethod = "wechat"
PayMethodApple PayMethod = "apple"
)
// 请求和响应结构体定义
type CreateOrderRequest struct {
PayMethod PayMethod `json:"pay_method"` // 支付方式alipay, wechat, apple
ProductEn string `json:"product_en"` // 商品英文名
}
type CreateOrderResponse struct {
OrderNo string `json:"order_no"` // 订单号
PayData interface{} `json:"pay_data"` // 支付数据
PayMethod PayMethod `json:"pay_type"` // 支付方式
OrderID int64 `json:"order_id"` // 订单ID
}
type RefundRequest struct {
OrderId int64 `json:"order_id"` // 订单ID
RefundAmount float64 `json:"refund_amount"` // 退款金额
Reason string `json:"reason"` // 退款原因
}
// 定义退款结果结构
type refundResult struct {
refundNo string
status string
refundTime time.Time
refundAmount float64
platformRefundId sql.NullString
}
type RefundResponse struct {
RefundNo string `json:"refund_no"` // 退款单号
Amount float64 `json:"amount"` // 退款金额
Status string `json:"status"` // 退款状态
}
type OrderStatusResponse struct {
OrderNo string `json:"order_no"` // 订单号
PayMethod PayMethod `json:"pay_type"` // 支付方式
PayStatus string `json:"pay_status"` // 支付状态
OrderStatus string `json:"order_status"` // 订单状态
Amount float64 `json:"amount"` // 订单金额
}