🎨 完善订单和微信支付功能

This commit is contained in:
2025-07-25 23:02:43 +08:00
parent 8fd1968cf6
commit bb2a68fb61
9 changed files with 572 additions and 41 deletions

245
utils/wechat/pay.go Normal file
View File

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

View File

@@ -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 {