Files
lckt-server/utils/wechat/pay.go
2025-07-25 23:02:43 +08:00

246 lines
9.3 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 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
}