一、前置条件
(1)go语言,1.18
(2)Gin、第三方依赖包:gopay【github.com/go-pay/gopay/alipay】https://github.com/go-pay/gopay/blob/main/doc/wechat_v3.md
(3)微信支付相关信息:appID ,商户ID,apiclient_key.pem等;产品能力概览 | 微信支付商户平台文档中心
(4)外网可访问的域名,用于支付成功回调;本地开发自测的话,搞个内网穿透工具;我用的花生壳,花了6块钱买了个域名【当时搞活动买1年,送98年】
(5)基于域名创建映射
(6)启动本地服务,验证映射是否成功
准备工作完成,开始编码!!
二、支付客户端代码:wechatClientV3
(1)保证单例,用go的sync.Once实现,具体代码如下
package lib
import (
"fmt"
"github.com/go-pay/gopay/wechat/v3"
"io/ioutil"
"log"
. "go_online_pay/config"
"sync"
)
var wechatClientV3 *wechat.ClientV3
var wcOnce sync.Once
//GetWechatClientV3 wechatClientV3
func GetWechatClientV3() *wechat.ClientV3 {
var err error
wcOnce.Do(func() {
cf := Conf{}
conf := cf.GetConf()
wechatClientV3, err = wechat.NewClientV3(conf.WxMchId, conf.WxSerialNo, conf.WxApiV3Key, ReadPem())
if err != nil {
log.Panic(err.Error())
}
// 启用自动同步返回验签,并定时更新微信平台API证书(开启自动验签时,无需单独设置微信平台API证书和序列号)
err = wechatClientV3.AutoVerifySign()
if err != nil {
log.Panic(err.Error())
}
})
return wechatClientV3
}
func ReadPem() string {
privateKey, err := ioutil.ReadFile("config/apiclient_key.pem")
if err != nil {
fmt.Println(err.Error())
}
return string(privateKey)
}
(2)基础配置信息,都放到conf.yaml文件内了,同时配套conf.go文件,main.go启动加载conf.yaml配置内容,后续通过conf.Xx获取属性值【config.WxMchId】,根据自己项目的实际情况,放置相关配置信息;微信安全要求使用本地证书方式,【apiclient_key.pem】这个文件很重要,前端也需要使用到,拉起微信授权会用到!!!
#--------------红网小程序微信支付:测试账号----------------
#微信-APPID(收款码
wx_app_id:
#商户ID 或者服务商模式的 sp_mchid
wx_mch_id:
#商户API证书的证书序列号
wx_serial_no:
#APIv3Key,商户平台获取
wx_api_v3_Key:
#商户API证书下载后,私钥 apiclient_key.pem 读取后的字符串内容
wx_private_key:
#应用密钥 AppSecret,在微信开放平台提交应用审核通过后获得
wx_app_secret:
#微信支付回调地址
wx_notify_url: https://XXX/wxPayNotify
三、支付服务类代码:WxPayService
(1)代码中含有:当面付-收款码链接,小程序支付,查询支付结果,退款,查询退款结果
package service
import (
"context"
"fmt"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/wechat/v3"
"go_online_pay/lib"
)
type WxPayService struct {
}
//WxJsAPI 支付,在微信支付服务后台生成预支付交易单
func (WxPayService) WxJsAPI(bm gopay.BodyMap) (string, string) {
wxClient := lib.GetWechatClientV3()
wxRsp, err := wxClient.V3TransactionJsapi(context.Background(), bm)
if err != nil {
fmt.Println(fmt.Sprintf("-----wxPay WxJsAPI() -------error:%s", err.Error()))
return "", err.Error()
}
return wxRsp.Response.PrepayId, wxRsp.Error
}
//WxNative 当面付 扫码支付,获取二维码
func (WxPayService) WxNative(bm gopay.BodyMap) string {
wxClient := lib.GetWechatClientV3()
wxRsp, err := wxClient.V3TransactionNative(context.Background(), bm)
if err != nil {
fmt.Println(fmt.Sprintf("-----wxPay WxNative() -------error:%s", err.Error()))
return ""
}
return wxRsp.Response.CodeUrl
}
//WxRefund 退款
func (WxPayService) WxRefund(bm gopay.BodyMap) (*wechat.RefundRsp, string) {
wxClient := lib.GetWechatClientV3()
wxRsp, err := wxClient.V3Refund(context.Background(), bm)
if err != nil {
fmt.Println(fmt.Sprintf("-----wxPay WxRefund() -------error:%s", err.Error()))
return nil, err.Error()
}
return wxRsp, wxRsp.Error
}
//WxQueryRefund 查询退款状态
func (WxPayService) WxQueryRefund(refundNo string) (*wechat.RefundQueryRsp, error) {
wxClient := lib.GetWechatClientV3()
wxRsp, err := wxClient.V3RefundQuery(context.Background(), refundNo, nil)
if err != nil {
fmt.Println(fmt.Sprintf("-----wxPay WxQueryRefund() -------error:%s", err.Error()))
}
return wxRsp, err
}
//WxTestV3Query 交易查询
func (WxPayService) WxTestV3Query(no string) *wechat.QueryOrderRsp {
wxClient := lib.GetWechatClientV3()
wxRsp, err := wxClient.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, no)
if err != nil {
fmt.Println(fmt.Sprintf("-----wxPay TestV3QueryOrder() -------error:%s", err.Error()))
return nil
}
return wxRsp
}
四、具体方法使用
(1)小程序支付部分代码
//第三方30分钟过期
expire := time.Now().Add(30 * 60 * time.Second).Format(time.RFC3339)
//商品名称,截取40个字
description := util.ExtractStrByLen(orderInfo.CommodityName, 40)
wxMap := make(gopay.BodyMap)
wxMap.Set("appid", config.GlobalConf.WxAppId).
Set("description", description).
Set("out_trade_no", payNo).
Set("time_expire", expire).
Set("notify_url", config.GlobalConf.WxNotifyUrl).
//订单总金额,单位为分。
SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", payAmountInt).
Set("currency", "CNY")
}).
SetBodyMap("payer", func(bm gopay.BodyMap) {
bm.Set("openid", openId)
})
wxPrepayId, errString := WxPayService{}.WxJsAPI(wxMap)
fmt.Println(fmt.Sprintf("-----wxPay WxJsAPI() 返回值 ----wxPrepayId=> %s ---errString -> %s", wxPrepayId, errString))
if len(wxPrepayId) == 0 {
errMap := util.JsonToMap(errString)
if val, ok := errMap["message"]; ok {
result["errorMsg"] = "拉起微信支付失败:" + val.(string)
return e.ERROR, result
}
result["errorMsg"] = "拉起微信支付失败,无WxPrepayId"
return e.ERROR, result
}
result["wx_prepay_Id"] = wxPrepayId
前端拿到wx_prepay_Id后,通过sdk拉起微信APP
(2) 其他方法,都可以仿照小程序入参方式,根据微信接口文档填写相应的key即可
(3)支付回调方法代码:回调后写入队列,异步处理
/WxPayNotify 解析微信回调请求的参数到 V3NotifyReq 结构体
func WxPayNotify(c *gin.Context) {
logger.Info("---------------------WxPayNotify START -------------")
// c.Request 是 gin 框架的写法
notifyReq, err := wechat.V3ParseNotify(c.Request)
// wxPublicKey 通过 client.WxPublicKey() 获取
wxClient := lib.GetWechatClientV3()
//异步通知验签
err = notifyReq.VerifySignByPK(wxClient.WxPublicKey())
if err != nil {
logger.Info("------WxPayNotify ERR -------------,err:", err.Error())
return
}
// 普通支付通知解密
result, err := notifyReq.DecryptCipherText(config.GlobalConf.WxApiV3Key)
if result.TradeState == "SUCCESS" {
var wxReq = make(map[string]interface{})
//商户订单号:商户系统内部订单号
wxReq["pay_no"] = result.OutTradeNo
//微信支付订单号:微信支付系统生成的订单号。
wxReq["trade_no"] = result.TransactionId
//与支付宝同步
wxReq["trade_status"] = "TRADE_SUCCESS"
wxReq["notify_time"] = result.SuccessTime
wxReq["total_amount"] = result.Amount.Total
wxReq["receipt_amount"] = result.Amount.PayerTotal
var mapData = make(map[string]interface{})
mapData["data_type"] = "PayNotify"
mapData["param"] = map[string]interface{}{"payType": "wxPay", "notifyReq": wxReq}
reqJson, _ := json.Marshal(mapData)
lib.PushMessToPayQueue(reqJson)
}
/*var wxReq = make(map[string]interface{})
//商户订单号:商户系统内部订单号
wxReq["pay_no"] = ""
//微信支付订单号:微信支付系统生成的订单号。
wxReq["trade_no"] = ""
//与支付宝同步
wxReq["trade_status"] = "TRADE_SUCCESS"
wxReq["notify_time"] = "2022-11-28T14:00:20+08.00"
wxReq["total_amount"] = 100000
wxReq["receipt_amount"] = 100000
var mapData = make(map[string]interface{})
mapData["data_type"] = "PayNotify"
mapData["param"] = map[string]interface{}{"payType": "wxPay", "notifyReq": wxReq}
reqJson, _ := json.Marshal(mapData)
lib.PushMessToPayQueue(reqJson)*/
// 此写法是 gin 框架返回微信的写法
c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.SUCCESS, Message: "成功"})
logger.Info("---------------------WxPayNotify END -------------")
return
}