diff --git a/api/v1/app/order.go b/api/v1/app/order.go index 6e9fd3f..e907fea 100644 --- a/api/v1/app/order.go +++ b/api/v1/app/order.go @@ -1,3 +1,131 @@ package app +import ( + "git.echol.cn/loser/lckt/global" + "git.echol.cn/loser/lckt/model/app" + "git.echol.cn/loser/lckt/model/app/request" + r "git.echol.cn/loser/lckt/model/common/response" + "git.echol.cn/loser/lckt/utils/user_jwt" + "git.echol.cn/loser/lckt/utils/wechat" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + type OrderApi struct{} + +// CreateOrder APP新建订单 +func (o *OrderApi) CreateOrder(c *gin.Context) { + var p app.Order + if err := c.ShouldBind(&p); err != nil { + global.GVA_LOG.Error("支付订单参数错误", zap.Error(err)) + r.FailWithMessage("支付订单参数错误", c) + return + } + + order, err := orderService.Create(&p) + if err != nil { + global.GVA_LOG.Error("创建订单失败", zap.Error(err)) + r.FailWithMessage("创建订单失败", c) + return + } + + r.OkWithData(order, c) +} + +func (o *OrderApi) PayOrder(context *gin.Context) { + var p request.PayReq + if err := context.ShouldBind(&p); err != nil { + global.GVA_LOG.Error("支付订单参数错误", zap.Error(err)) + r.FailWithMessage("支付订单参数错误", context) + return + } + + pay, err := orderService.Pay(p, context) + if err != nil { + global.GVA_LOG.Error("支付订单失败", zap.Error(err)) + r.FailWithMessage("支付订单失败:"+err.Error(), context) + return + } + + r.OkWithDetailed(pay, "支付订单成功", context) +} + +// NotifyOrder 微信支付回调通知 +func (o *OrderApi) NotifyOrder(context *gin.Context) { + err := wechat.NotifyHandle(context) + if err != nil { + global.GVA_LOG.Error("微信支付回调处理失败", zap.Error(err)) + r.FailWithMessage("微信支付回调处理失败:"+err.Error(), context) + return + } + r.OkWithMessage("微信支付回调处理成功", context) +} + +func (o *OrderApi) GetOrderDetail(context *gin.Context) { + id := context.Param("id") + if id == "" { + global.GVA_LOG.Error("获取订单详情参数错误: ID不能为空") + r.FailWithMessage("获取订单详情参数错误: ID不能为空", context) + return + } + + order, err := orderService.GetOrderDetail(id) + if err != nil { + global.GVA_LOG.Error("获取订单详情失败", zap.Error(err)) + r.FailWithMessage("获取订单详情失败", context) + return + } + + r.OkWithData(order, context) +} + +func (o *OrderApi) GetOrderList(context *gin.Context) { + var p request.GetOrderList + if err := context.ShouldBind(&p); err != nil { + global.GVA_LOG.Error("获取订单列表参数错误", zap.Error(err)) + r.FailWithMessage("获取订单列表参数错误", context) + return + } + + // 获取当前用户ID + userId := user_jwt.GetUserID(context) + if userId == 0 { + global.GVA_LOG.Error("获取用户ID失败") + r.FailWithMessage("获取用户ID失败", context) + return + } + + // 获取订单列表 + orders, total, err := orderService.GetOrderList(p, userId) + if err != nil { + global.GVA_LOG.Error("获取订单列表失败", zap.Error(err)) + r.FailWithMessage("获取订单列表失败:"+err.Error(), context) + return + } + + r.OkWithDetailed( + r.PageResult{ + List: orders, + Total: total, + Page: p.Page, + PageSize: p.PageSize, + }, "获取订单列表成功", context) +} + +func (o *OrderApi) BalancePay(context *gin.Context) { + var p request.BalancePay + if err := context.ShouldBind(&p); err != nil { + global.GVA_LOG.Error("余额支付参数错误", zap.Error(err)) + r.FailWithMessage("余额支付参数错误", context) + return + } + + err := orderService.BalancePay(p) + if err != nil { + global.GVA_LOG.Error("余额支付失败", zap.Error(err)) + r.FailWithMessage("余额支付失败:"+err.Error(), context) + return + } + + r.OkWithMessage("余额支付成功", context) +} diff --git a/initialize/router.go b/initialize/router.go index 41ecd2f..305a29e 100644 --- a/initialize/router.go +++ b/initialize/router.go @@ -119,6 +119,7 @@ func Routers() *gin.Engine { { appRouter.InitAppUserRouter(AppAuthGroup, PublicGroup) appRouter.InitBannerRouter(PrivateGroup, PublicGroup) // Banner相关路由 + appRouter.InitOrderRouter(AppAuthGroup) // 订单相关路由 } //插件路由安装 diff --git a/main.go b/main.go index eac1495..53a57b4 100644 --- a/main.go +++ b/main.go @@ -44,5 +44,6 @@ func main() { } wechat.InitWeOfficial() // 初始化微信公众号SDK + wechat.InitWechatPay() // 初始化微信支付SDK core.RunWindowsServer() } diff --git a/model/app/order.go b/model/app/order.go index 89c3d6c..2718b4b 100644 --- a/model/app/order.go +++ b/model/app/order.go @@ -8,12 +8,15 @@ type Order struct { UserId uint64 `gorm:"column:user_id;type:bigint(20) unsigned;comment:用户ID;NOT NULL" json:"user_id"` OrderType int `gorm:"column:order_type;type:int(11);comment:订单类型 |1 课程|2 vip|;NOT NULL" json:"order_type"` ArticleId uint `gorm:"column:article_id;type:bigint(20) unsigned;default:0;comment:文章ID;NOT NULL" json:"article_id"` + Title string `gorm:"column:title;type:varchar(255);comment:订单标题;NOT NULL" json:"title"` Name string `gorm:"column:name;type:varchar(255);comment:名称;NOT NULL" json:"name"` Price int64 `gorm:"column:price;type:int(11) unsigned;default:0;comment:订单价格;NOT NULL" json:"price"` Phone string `gorm:"column:phone;type:varchar(11);comment:手机号;NOT NULL" json:"phone"` TeacherId uint64 `gorm:"column:teacher_Id;type:bigint(20);comment:教师Id;NOT NULL" json:"teacher_Id"` Status int `gorm:"column:status;type:int(11);default:1;comment:订单状态 |1 未付款|2 已付款|3 已过期|;NOT NULL" json:"status"` Desc string `gorm:"column:desc;type:varchar(24);comment:订单描述;NOT NULL" json:"desc"` + OpenId string `gorm:"column:open_id;type:varchar(64);comment:用户OpenId;NOT NULL" json:"open_id"` + PayType int `gorm:"column:pay_type;type:int(11);default:1;comment:支付方式 |1 微信|2 支付宝|3 余额|;NOT NULL" json:"pay_type"` } // TableName Order表 diff --git a/model/app/request/order.go b/model/app/request/order.go new file mode 100644 index 0000000..43f0d82 --- /dev/null +++ b/model/app/request/order.go @@ -0,0 +1,23 @@ +package request + +import common "git.echol.cn/loser/lckt/model/common/request" + +type BalancePay struct { + UserId uint `json:"user_id" binding:"required"` // 用户ID + OrderNo string `json:"order_no" binding:"required"` // 订单号 + OrderId uint `json:"order_id" binding:"required"` // 订单ID +} + +type GetOrderList struct { + common.PageInfo + Status int `json:"status" form:"status"` // 订单状态 +} + +type PayReq struct { + UserId uint64 `json:"user_id"` // 用户ID + OrderId uint `json:"order_id"` + Mode string `json:"mode"` // h5 jsapi + WxCode string `json:"wx_code"` + PayMethod int `json:"pay_method" vd:"$>0; msg:'参数不能为空'"` + OrderNo string `json:"order_no" vd:"@:len($)>0; msg:'订单编号参数不能为空'"` +} diff --git a/router/app/order.go b/router/app/order.go new file mode 100644 index 0000000..b9baece --- /dev/null +++ b/router/app/order.go @@ -0,0 +1,18 @@ +package app + +import "github.com/gin-gonic/gin" + +type OrderRouter struct{} + +// InitOrderRouter 初始化订单路由 +func (r *OrderRouter) InitOrderRouter(AppRouter *gin.RouterGroup) { + appRouter := AppRouter.Group("app_order") + { + appRouter.POST("", orderApi.CreateOrder) // 创建订单 + appRouter.POST("/wechat/pay", orderApi.PayOrder) // 微信支付订单 + appRouter.GET("/list", orderApi.GetOrderList) // 获取订单列表 + appRouter.GET(":id", orderApi.GetOrderDetail) // 获取订单详情 + appRouter.POST("/balance/pay", orderApi.BalancePay) // 余额支付 + appRouter.POST("/notify", orderApi.NotifyOrder) // 微信支付回调通知 + } +} diff --git a/service/app/order.go b/service/app/order.go index e1c6dab..64093ef 100644 --- a/service/app/order.go +++ b/service/app/order.go @@ -1,8 +1,160 @@ package app +import ( + "fmt" + "git.echol.cn/loser/lckt/global" + "git.echol.cn/loser/lckt/model/app" + "git.echol.cn/loser/lckt/model/app/request" + "git.echol.cn/loser/lckt/model/user" + "git.echol.cn/loser/lckt/utils/wechat" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + type OrderService struct{} // Pay 发起支付 -func (s *OrderService) Pay(orderId string, userId int64) (string, error) { +func (s *OrderService) Pay(p request.PayReq, ctx *gin.Context) (interface{}, error) { + order := app.Order{} + err := global.GVA_DB.Where("id = ? AND order_no = ?", p.OrderId, p.OrderNo).First(&order).Error + if err != nil { + global.GVA_LOG.Error("查询订单失败", zap.Error(err)) + return "", err + } + + if order.Status == 3 { + global.GVA_LOG.Error("订单已过期", zap.Int64("order_id", int64(order.ID))) + return "", fmt.Errorf("订单已过期") + } + + if p.Mode == "h5" { + payConf, err := wechat.H5Pay(&order, ctx) + if err != nil { + global.GVA_LOG.Error("微信支付订单失败", zap.Error(err)) + return "", err + } + return payConf, nil + } else if p.Mode == "jsapi" { + payConf, err := wechat.JSAPIPay(&order, ctx) + if err != nil { + global.GVA_LOG.Error("微信支付订单失败", zap.Error(err)) + return "", err + } + return payConf, nil + } + return "", nil } + +func (s *OrderService) Create(o *app.Order) (*app.Order, error) { + // 生成订单号 + o.OrderNo = wechat.GenerateOrderNum() + + // 查询订单商品价格 + price := 0 + if o.OrderType == 1 { + err := global.GVA_DB.Table("article").Select("price").Where("id = ?", o.ArticleId).Scan(&price).Error + if err != nil { + global.GVA_LOG.Error("查询商品价格失败", zap.Error(err)) + return nil, err + } + } else { + err := global.GVA_DB.Table("lckt_vip").Select("price").Where("id = ?", o.ArticleId).Scan(&price).Error + if err != nil { + global.GVA_LOG.Error("查询VIP价格失败", zap.Error(err)) + return nil, err + } + } + o.Price = int64(price) + o.Status = 1 // 设置订单状态为未付款 + // 设置openid + openId := "" + err := global.GVA_DB.Table("app_user").Where("id = ?", o.UserId).Select("open_id").Scan(&openId).Error + if err != nil { + global.GVA_LOG.Error("查询用户OpenID失败,请检查微信是否绑定", zap.Error(err)) + return nil, fmt.Errorf("查询用户OpenID失败,请检查微信是否绑定") + } + o.OpenId = openId + + err = global.GVA_DB.Create(&o).Error + if err != nil { + global.GVA_LOG.Error("创建订单失败", zap.Error(err)) + return nil, err + } + return o, nil +} + +func (s *OrderService) GetOrderDetail(id string) (order app.Order, err error) { + err = global.GVA_DB.Where("id = ?", id).First(&order).Error + if err != nil { + global.GVA_LOG.Error("获取订单详情失败", zap.Error(err)) + return app.Order{}, err + } + return order, nil +} + +func (s *OrderService) BalancePay(p request.BalancePay) error { + order := app.Order{} + err := global.GVA_DB.Where("id = ? AND order_no = ?", p.OrderId, p.OrderNo).First(&order).Error + if err != nil { + global.GVA_LOG.Error("查询订单失败", zap.Error(err)) + return err + } + + if order.Status != 1 { + global.GVA_LOG.Error("订单状态错误,无法支付", zap.Int("status", order.Status)) + return err + } + // 检查用户余额是否足够 + var user user.User + err = global.GVA_DB.Where("id = ?", p.UserId).Select("id,balance").First(&user).Error + if err != nil { + global.GVA_LOG.Error("查询用户信息失败", zap.Error(err)) + return err + } + // 将user.Balance转为int64类型进行比较 + balance := int64(user.Balance) + if balance < order.Price/100 { // 订单价格是以分为单位存储的 + global.GVA_LOG.Error("用户余额不足", zap.Int64("balance", balance), zap.Int64("order_price", order.Price)) + return fmt.Errorf("用户余额不足") + } + + // 扣除用户余额 + balance -= order.Price / 100 + err = global.GVA_DB.Model(&user).Where("id = ?", p.UserId).Update("balance", balance).Error + if err != nil { + global.GVA_LOG.Error("扣除用户余额失败", zap.Error(err)) + return err + } + // 更新订单状态为已付款 + order.Status = 2 + err = global.GVA_DB.Model(&order).Where("id = ?", order.ID).Update("status", 2).Error + if err != nil { + global.GVA_LOG.Error("更新订单状态失败", zap.Error(err)) + return err + } + + global.GVA_LOG.Info("余额支付成功", zap.Int64("user_id", int64(p.UserId)), zap.String("order_no", order.OrderNo)) + return nil +} + +func (s *OrderService) GetOrderList(p request.GetOrderList, id uint) (orders []app.Order, total int64, err error) { + limit := p.PageSize + offset := p.PageSize * (p.Page - 1) + + db := global.GVA_DB.Model(&app.Order{}).Where("user_id = ?", id) + if p.Keyword != "" { + db = db.Where("title LIKE ? ", "%"+p.Keyword+"%") + } + if p.Status != 0 { + db = db.Where("status = ?", p.Status) + } + + err = db.Count(&total).Error + err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&orders).Error + if err != nil { + global.GVA_LOG.Error("获取订单列表失败", zap.Error(err)) + return nil, 0, err + } + return +} diff --git a/utils/wechat/pay.go b/utils/wechat/pay.go new file mode 100644 index 0000000..803ad2d --- /dev/null +++ b/utils/wechat/pay.go @@ -0,0 +1,245 @@ +package wechat + +import ( + gfmt "fmt" + "git.echol.cn/loser/lckt/global" + "git.echol.cn/loser/lckt/model/app" + "github.com/ArtisanCloud/PowerLibs/v3/fmt" + "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models" + "github.com/ArtisanCloud/PowerWeChat/v3/src/payment" + wxRequst "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/notify/request" + "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/order/request" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "log" + "math/rand" + "time" +) + +var WechatPay *payment.Payment + +// InitWechatPay 初始化微信支付 +func InitWechatPay() { + PaymentService, err := payment.NewPayment(&payment.UserConfig{ + AppID: "wx3d21df18d7f8f9fc", // 小程序、公众号或者企业微信的appid + MchID: global.GVA_CONFIG.Pays[0].MchId, // 商户号 appID + MchApiV3Key: "1a3sd8561d5179Df152D4789aD38wG9s", // 微信V3接口调用必填 + Key: "57s14dFG915486Sd5617f23d45f671Ad", + CertPath: `-----BEGIN CERTIFICATE----- +MIIEKzCCAxOgAwIBAgIUWaiR+0A+x6HPIJDbnI7HBL1DsQEwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT +FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg +Q0EwHhcNMjUwNzIzMDU1MDU5WhcNMzAwNzIyMDU1MDU5WjCBhDETMBEGA1UEAwwK +MTY0Njg3NDc1MzEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTAwLgYDVQQL +DCfmtbflj6PpvpnljY7pk4HlnZrmiJDnlLXlrZDllYbliqHllYbooYwxCzAJBgNV +BAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMUD4rf7ct0zUxJKGww+8c38L/IBx9yeNpOc7lav+CEwqZvieesy +p1I2hbY+hwOZm23//0Vxcnl6fkr5wdidE/RsN808sGkYsetJ6q7LZsgsxnbaQd7w +rBc4GlOh1Q4teErbjNTUMuozYzikEjMjgZMxs7i5WJnFMMVvgJS4681UKH8MQG93 +BnaaD5zEyofAnOov7mvC0KZ0RpqwiLdUsMaPzYILzccOv7Tj82Z/qLw8AFcI+7L1 +mq8trZxmSPXP5Fypz+1VKLKR9MaELUuQLPouewDjXkKTBnxkhoWow6huuz3qyUUo +i7uJtnmDn6Eu+60ch61BB5ws3rhWDpltR/0CAwEAAaOBuTCBtjAJBgNVHRMEAjAA +MAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2 +Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJD +MDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJC +MjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBk +WmviQM60S/l4I4/RtQWtiZS+85fk2ePVSk5gBUS27W4yTw+aXy5RYaCLcOVXTXje +PpAfE7QvkjZxMg/k0SJR0/NbgDNF6xuCbMj78+czWVCkqHwPjY4hWRdsM9s44Orp +lwFrG/UNRhPgXcdwNQj8MNzy3nGeO0HWbJFBWcuRhSSwXpxv3Sh6pZviL3QA+lrV +7QB7d0fEu8sSGbwnIB0oXl7y78l2/D20p57TKHsq5IPfQfDdExNXKV60ikMt/MdP +C4ygbeRpSAobr6qMTXcpWlbH3KfIPOUFny3nkN8C6lm//QUl7ynOXkECcmIHY6XF +yMx70ORdoBl1DaZ369P9 +-----END CERTIFICATE----- +`, // 微信V2接口调用必填 + KeyPath: `-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFA+K3+3LdM1MS +ShsMPvHN/C/yAcfcnjaTnO5Wr/ghMKmb4nnrMqdSNoW2PocDmZtt//9FcXJ5en5K ++cHYnRP0bDfNPLBpGLHrSequy2bILMZ22kHe8KwXOBpTodUOLXhK24zU1DLqM2M4 +pBIzI4GTMbO4uViZxTDFb4CUuOvNVCh/DEBvdwZ2mg+cxMqHwJzqL+5rwtCmdEaa +sIi3VLDGj82CC83HDr+04/Nmf6i8PABXCPuy9ZqvLa2cZkj1z+Rcqc/tVSiykfTG +hC1LkCz6LnsA415CkwZ8ZIaFqMOobrs96slFKIu7ibZ5g5+hLvutHIetQQecLN64 +Vg6ZbUf9AgMBAAECggEBAI4q66PQM2cj7kI4b6Q6l8sIvKBqYIr3MHL8v5CWkvuA +XiQ7Hbd3af6NkZedL1iNs/eAz/iQkQbQOepoqFVjpE6w+OOFc9ejFmCvikZwSM8S +YHTLstTp34Ux2u2WzmPYtAFwxQOfzM3sHyF0ZB269Xn+V65pMWJlRXhzqdmoR6B2 +YYLzHVBZAbgj7IlS+f1WPkkbrb3BIs71tYyPS3Ng6nhGgXrnqmE5fhm93wR9KR5R +RcgzFGH/8qD2LndmJz8tYtjvlq016Pp40VrYabIrvX6apglayShLQ+2XUe8VKH2Q +dQPIKL3R0ZeDeqwn4PTMclBYd/nXFSbIQndufTDY84ECgYEA8RTcCPOFYkoDeQIr +yKqWo1P1cKsB/bGzvLmo+FqFQij5lmj9iDdvpSQJ58Uj+TFy5qNRT6VueF8U4pNd +Fmcbw05cH+9NzAXji6aYWCargbmhW4L+rzub+TQmdAfqXbBknVgrLenY4LFnFFm1 +SIdJRid3vCwiRuhcjecg4FasR+kCgYEA0TTw4ZGxPCX5qt00PUFvuGpqFxFqZ7Ah +wapwQTUY48WeC0Pg7Iq6ipnhjggkgYLkGc1klmTwJXKV0acIS5/DNclepMWZPwIc ++GcKclQsy8MHm+L2tPGBQosRGiSrXi5LzBIQsYSyhBRDUSqu+yj7TTd2XqpUkLAw +qdrWiIjVBvUCgYAOkh0uaVGBfEmzcZ8l1LGgE339HkjThX8AhBQjVo1BT2quXZAd +QIR97ayvlmmzMPrp16sdbjk8Czse6pswtHCoID9PKs5/60cydJI2mbe58nc/Ka6s +9qRZrn44exX+LaAXJnINp1mVUwOQ5k8foBWcqNwCwoQb1wVpCjQhevuUqQKBgQC1 +SA+3Fr0iprF6iqWastoxThzSEmhGowwNOjh9eJoxvOsfTdlYfzn3ojIeFhY0F4y6 +gw03eQ3TFUCXZAq/JRhNwkl9tC//tkAOS5N00FXk1wH/5aLr1h2w4LqYEdBhEvLh +SYInoRnjc3+FlNv9jVx9Y6Lxkt0mZ1YzyQp/UzptBQKBgFTOonik05rMHGlEg9Ng +unjDAEK+7tXhM/RKxWDs7pZeGbdl1c/NDOX4wIbuK2cCkV0v/qfg6Ex2Obf5rqM4 +R5lrs9y30rZbwmfms8G3cimTAsOcLqObKkBED+g07t+3bw44JXLwYLPm/z//bIcI +fkxyxKL5ZJhP3/bPJJj8UAq3 +-----END PRIVATE KEY----- +`, + SerialNo: global.GVA_CONFIG.Pays[0].SerialNo, // 商户支付证书序列号 + NotifyURL: global.GVA_CONFIG.Pays[0].NotifyUrl, // 微信支付回调地址 + HttpDebug: false, + Log: payment.Log{ + Level: "debug", + // 可以重定向到你的目录下,如果设置File和Error,默认会在当前目录下的wechat文件夹下生成日志 + File: "/Users/user/wechat/payment/info.log", + Error: "/Users/user/wechat/payment/error.log", + Stdout: false, // 是否打印在终端 + }, + Http: payment.Http{ + Timeout: 30.0, + BaseURI: "https://api.mch.weixin.qq.com", + }, + }) + + if err != nil { + log.Println("初始化微信支付 SDK失败", err) + } + WechatPay = PaymentService +} + +// H5Pay 发送订单 +func H5Pay(order *app.Order, ctx *gin.Context) (interface{}, error) { + options := &request.RequestH5Prepay{ + Amount: &request.H5Amount{ + Total: int(order.Price), + Currency: "CNY", + }, + Attach: order.Desc, + Description: order.Desc, + OutTradeNo: order.OrderNo, // 这里是商户订单号,不能重复提交给微信 + SceneInfo: &request.H5SceneInfo{ + PayerClientIP: ctx.ClientIP(), // 用户终端IP + H5Info: &request.H5H5Info{ + Type: "Wap", + AppName: "老陈课堂", + }, + }, + } + // 下单 + response, err := WechatPay.Order.TransactionH5(ctx, options) + + if err != nil { + global.GVA_LOG.Error("微信支付下单失败", zap.Error(err)) + return "", err + } + /* + payConf, err := WechatPay.JSSDK.BridgeConfig(response.PrepayID, true) + if err != nil { + global.GVA_LOG.Error("获取微信支付配置失败", zap.Error(err)) + return "", err + }*/ + global.GVA_LOG.Info("微信支付配置", zap.Any("payConf", response)) + return response, nil +} + +func JSAPIPay(order *app.Order, ctx *gin.Context) (interface{}, error) { + options := &request.RequestJSAPIPrepay{ + Amount: &request.JSAPIAmount{ + Total: int(order.Price), + Currency: "CNY", + }, + Attach: order.Name, + Description: order.Desc, + OutTradeNo: order.OrderNo, // 这里是商户订单号,不能重复提交给微信 + Payer: &request.JSAPIPayer{ + OpenID: order.OpenId, // 用户的openid, 记得也是动态的。 + }, + } + + // 下单 + response, err := WechatPay.Order.JSAPITransaction(ctx, options) + + if err != nil { + panic(err) + } + + // payConf大致如下: + // { + // "appId": "ww16143ea0101327c7", + // "nonceStr": "jcMfo7lsfM3LPWkLRJb7rQU6WeatStEU", + // "package": "prepay_id=xxxxx", + // "paySign": "hSPF2wU0aYeTq+DJ14ELM...省略部分数据...RrOmlEkZXVxqCdZmniLdA==", + // "signType": "RSA", + // "timeStamp": "1634305101" + // } + payConf, err := WechatPay.JSSDK.BridgeConfig(response.PrepayID, true) + if err != nil { + global.GVA_LOG.Error("获取微信支付配置失败", zap.Error(err)) + return "", err + } + + return payConf, nil +} + +// NotifyHandle 微信支付回调处理 +func NotifyHandle(ctx *gin.Context) error { + res, err := WechatPay.HandlePaidNotify( + ctx.Request, + func(message *wxRequst.RequestNotify, transaction *models.Transaction, fail func(message string)) interface{} { + // 看下支付通知事件状态 + // 这里可能是微信支付失败的通知,所以可能需要在数据库做一些记录,然后告诉微信我处理完成了。 + if message.EventType != "TRANSACTION.SUCCESS" { + fmt.Dump(transaction) + return true + } + + if transaction.OutTradeNo != "" { + // 这里对照自有数据库里面的订单做查询以及支付状态改变 + // 更新订单状态为已支付 + order := app.Order{} + err := global.GVA_DB.Model(&app.Order{}).Where("order_no = ?", transaction.OutTradeNo).First(&order).Error + if err != nil { + global.GVA_LOG.Error("查询订单失败", zap.Error(err)) + return nil + } + order.Status = 2 // 设置订单状态为已支付 + err = global.GVA_DB.Save(&order).Error + if err != nil { + global.GVA_LOG.Error("更新订单状态失败", zap.Error(err)) + return nil + } + log.Printf("订单号:%s 支付成功", transaction.OutTradeNo) + } else { + // 因为微信这个回调不存在订单号,所以可以告诉微信我还没处理成功,等会它会重新发起通知 + // 如果不需要,直接返回true即可 + fail("payment fail") + return nil + } + return true + }, + ) + + // 这里可能是因为不是微信官方调用的,无法正常解析出transaction和message,所以直接抛错。 + if err != nil { + res.StatusCode = 502 + err = res.Write(ctx.Writer) + return err + //panic(err) + } + + // 这里根据之前返回的是true或者fail,框架这边自动会帮你回复微信 + err = res.Write(ctx.Writer) + + if err != nil { + panic(err) + } + global.GVA_LOG.Info("微信支付回调处理成功", zap.Any("response", res)) + return nil +} + +func GenerateOrderNum() string { + now := time.Now() + dateStr := now.Format("060102") // 年的后2位+月+日 + seconds := int(now.Sub(time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())).Seconds()) + secondsStr := gfmt.Sprintf("%05d", seconds) + nanoStr := gfmt.Sprintf("%06d", now.Nanosecond()/1000) // 微秒级,6位 + rand.Seed(time.Now().UnixNano()) + randStr := gfmt.Sprintf("%03d", rand.Intn(1000)) // 3位随机数 + orderNo := gfmt.Sprintf("%s%s%s%s%s", "5258", dateStr, secondsStr, nanoStr, randStr) + return orderNo +} diff --git a/utils/wechat/wechat.go b/utils/wechat/wechat.go index c4f26a9..1f2d12b 100644 --- a/utils/wechat/wechat.go +++ b/utils/wechat/wechat.go @@ -3,15 +3,12 @@ package wechat import ( "git.echol.cn/loser/lckt/global" "github.com/ArtisanCloud/PowerSocialite/v3/src/providers" - "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" "github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount" - "github.com/ArtisanCloud/PowerWeChat/v3/src/payment" "go.uber.org/zap" "log" ) var WeOfficial *officialAccount.OfficialAccount -var WechatPay *payment.Payment // InitWeOfficial 初始化微信公众号 func InitWeOfficial() { @@ -37,43 +34,6 @@ func InitWeOfficial() { WeOfficial = OfficialAccountApp } -// InitWechatPay 初始化微信支付 -func InitWechatPay() { - PaymentService, err := payment.NewPayment(&payment.UserConfig{ - AppID: "wx3d21df18d7f8f9fc\n", // 小程序、公众号或者企业微信的appid - MchID: "[mch_id]", // 商户号 appID - MchApiV3Key: "1a3sd8561d5179Df152D4789aD38wG9s", // 微信V3接口调用必填 - Key: "57s14dFG915486Sd5617f23d45f671Ad", // 微信V2接口调用必填 - CertPath: "[wx_cert_path]", // 商户后台支付的Cert证书路径 - KeyPath: "[wx_key_path]", // 商户后台支付的Key证书路径 - SerialNo: "[serial_no]", // 商户支付证书序列号 - NotifyURL: "[notify_url]", // 微信支付回调地址 - HttpDebug: true, - Log: payment.Log{ - Level: "debug", - // 可以重定向到你的目录下,如果设置File和Error,默认会在当前目录下的wechat文件夹下生成日志 - File: "/Users/user/wechat/payment/info.log", - Error: "/Users/user/wechat/payment/error.log", - Stdout: false, // 是否打印在终端 - }, - Http: payment.Http{ - Timeout: 30.0, - BaseURI: "https://api.mch.weixin.qq.com", - }, - // 可选,不传默认走程序内存 - Cache: kernel.NewRedisClient(&kernel.UniversalOptions{ - Addrs: []string{"127.0.0.1:6379"}, - Password: "", - DB: 0, - }), - }) - - if err != nil { - log.Println("初始化微信支付 SDK失败", err) - } - WechatPay = PaymentService -} - func GetUserInfo(code string) *providers.User { userFromCode, err := WeOfficial.OAuth.UserFromCode(code) if err != nil {