Go 实现微信支付和回调

go 微信支付开发 jsapi
一、准备工作
1 . 注册企业账号: https://pay.weixin.qq.com/
2 . 配置V3 支付
3 . 绑定微信小程序
4 . 设置 下载证书
5 . 也可以手撸文档 https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/direct-jsons/jsapi-prepay.html
二、证书放在项目 cert中

image-20231010171100268.png

三、配置config.yml

1 . 注册企业账号: https://pay.weixin.qq.com/
2 . 配置V3 支付
3 . 绑定微信小程序
4 . 设置 下载证书
5 . 也可以手撸文档  https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/direct-jsons/jsapi-prepay.html

四、下载对应的SDK

go get github.com/wechatpay-apiv3/wechatpay-go

五、封装微信支付类 pay.go

package wepay

//支付公共uri
const (
  publicKeyUrl = "https://api.mch.weixin.qq.com/v3/certificates"
  commonPayUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"
)
//自定义回调uri
const (
  notifyUrl = "https://xxxxxxx/api/v1/order/notify"
)

// 微信支付
func JsApi(payResMap map[string]string) (payJson map[string]string, err error) {
appId := viper.GetString("mini.appId")
privatePath := viper.GetString("wePay.privatePath")
payMap := make(map[string]string)
timeStamp := time.Now().Unix()
nonce := waymon.GetRandomString(32)
packageStr := "prepay_id=" + payResMap["prepay_id"]
payMap["appId"] = appId
payMap["timeStamp"] = fmt.Sprintf("%v", timeStamp)
payMap["nonceStr"] = nonce
payMap["package"] = packageStr
// 签名
message := fmt.Sprintf("%s\n%s\n%s\n%s\n", appId, fmt.Sprintf("%v", timeStamp), nonce, packageStr)
dir, _ := os.Getwd()
open, err := os.Open(dir + privatePath)
if err != nil {
return payJson, err
}
defer open.Close()
privateKey, err := ioutil.ReadAll(open)
if err != nil {
return payJson, err
}
signBytes, err := signPKCS1v15(hasha256(message), privateKey, crypto.SHA256)
if err != nil {
return payJson, err
}
sign := base64EncodeStr(signBytes)
payMap["signType"] = "RSA"
payMap["paySign"] = sign
payJsonBytes, err := json.Marshal(payMap)
if err != nil {
return payJson, err
}
err = json.Unmarshal(payJsonBytes, &payJson)
if err != nil {
return nil, err
}
return payJson, nil
}


func CommonPay(tradeNo, desc, openId string, amount int) (payResMap map[string]string, err error) {
appId := viper.GetString("mini.appId")
mchId := viper.GetString("wePay.mchId")
payResMap = make(map[string]string)
paramMap := make(map[string]interface{})
paramMap["appid"] = appId
paramMap["mchid"] = mchId
paramMap["description"] = desc
paramMap["out_trade_no"] = tradeNo
paramMap["notify_url"] = notifyUrl
paramMap["amount"] = map[string]interface{}{
"total":    amount,
"currency": "CNY",
}
paramMap["payer"] = map[string]string{
"openid": openId,
}
token, err := authorization(http.MethodPost, paramMap, commonPayUrl)
if err != nil {
return payResMap, err
}
marshal, _ := json.Marshal(paramMap)
request, err := http.NewRequest(http.MethodPost, commonPayUrl, bytes.NewReader(marshal))
if err != nil {
return payResMap, err
}
request.Header.Add("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
request.Header.Add("User-Agent", "用户代理(https://zh.wikipedia.org/wiki/User_agent)")
request.Header.Add("Content-type", "application/json;charset='utf-8'")
request.Header.Add("Accept", "application/json")
client := http.DefaultClient
response, err := client.Do(request)
if err != nil {
return payResMap, err
}
defer func() {
response.Body.Close()
}()
bodyBytes, err := ioutil.ReadAll(response.Body)
if err != nil {
return payResMap, err
}
if err = json.Unmarshal(bodyBytes, &payResMap); err != nil {
return payResMap, err
}
if payResMap["prepay_id"] == "" {
return payResMap, errors.New("code:" + payResMap["code"] + "err:" + payResMap["message"])
}
return payResMap, nil
}

六、调用方法

payRes, err := wepay.CommonPay(service.TradeNo, service.Desc, service.OpenId, service.Money)
if err != nil {
code = e.Error
return res.Response{
Status: code,
Msg:    err.Error(),
Data:   nil,
}
}
pay, err := wepay.JsApi(payRes)
if err != nil {
code = e.Error
return res.Response{
Status: code,
Msg:    err.Error(),
Data:   nil,
}
}

pay : 返回给前端小程序调起支付

七、回调

func OrderNotify(c *gin.Context) {
aesKey := viper.GetString("wePay.aesKey")
defer func() {
//错误处理
if e := recover(); e != nil {
zap.S().Error("微信支付回调异常:", e)
log.Logger.Error("微信支付回调异常")
}
}()
request := notify.Request{}
c.ShouldBind(&request)
mapstructure.Decode(c.Params, &request)
if request.EventType == "TRANSACTION.SUCCESS" {
plaintext, err := wepay.DecryptAES256GCM(
aesKey, request.Resource.AssociatedData, request.Resource.Nonce, request.Resource.Ciphertext,
)
if err != nil {
fmt.Println(err)
zap.S().Error("DecryptAES256GCM err" + err.Error())
log.Logger.Error("DecryptAES256GCM err" + err.Error())
}
transaction := payments.Transaction{}
json.Unmarshal([]byte(plaintext), &transaction)
go func() {
  // 执行service层代码
}()
tmp := make(map[string]interface{})
tmp["code"] = "SUCCESS"
tmp["message"] = "成功"
tmpJson, _ := json.Marshal(tmp)
c.Writer.Write(tmpJson)
} else {
tmp := make(map[string]interface{})
tmp["code"] = "500"
tmp["message"] = "失败"
tmpJson, _ := json.Marshal(tmp)
c.Writer.Write(tmpJson)
}
}

八、代码

https://github.com/Waymon102092/WaymonWeXinPay

eg
github主页: https://github.com/Waymon102092/
微信公众号: Waymon

在验证微信支付回调通知的签名时,需要确保数据完整性请求来源的真实性。以下是使用JavaGo语言验证签名的方法。 ### Java验证签名方法 1. **获取回调参数** 从微信支付异步通知中获取所有请求参数,并过滤掉`sign`字段,因为该字段是签名值本身,不需要参与签名计算[^1]。 2. **按字典序排序参数** 将剩余参数按照字段名的ASCII顺序进行排序,以保证与微信服务器生成签名的方式一致[^1]。 3. **拼接待签名字符串** 将排序后的参数按照`key=value`的形式拼接成一个字符串,并在末尾追加商户私钥(API密钥)[^1]。 4. **生成MD5签名** 使用MD5算法对拼接后的字符串进行加密,得到签名值,并与回调中的`sign`字段进行比对[^1]。 示例代码如下: ```java import java.util.*; public class WeChatPaySignUtil { public static String generateSignature(Map<String, String> params, String apiKey) { // 过滤空值sign字段 Map<String, String> validParams = new HashMap<>(); for (Map.Entry<String, String> entry : params.entrySet()) { if (entry.getValue() != null && !entry.getKey().equals("sign")) { validParams.put(entry.getKey(), entry.getValue()); } } // 按照key的字典序排序 List<String> keys = new ArrayList<>(validParams.keySet()); Collections.sort(keys); // 拼接待签名字符串 StringBuilder sb = new StringBuilder(); for (String key : keys) { sb.append(key).append("=").append(validParams.get(key)).append("&"); } sb.append("key=").append(apiKey); // 生成MD5签名 return MD5Util.md5(sb.toString()).toUpperCase(); } } ``` ### Go验证签名方法 1. **解析回调JSON数据** 微信支付异步通知通常采用JSON格式返回数据,需先将其解析为结构体或Map类型[^3]。 2. **提取并排序参数** 同样排除`sign`字段后,将其他字段按照字段名排序,以确保与微信服务器端签名方式一致[^3]。 3. **拼接并生成HMAC-SHA256签名** 微信支付V3版本使用HMAC-SHA256算法签名,需使用商户私钥进行签名计算,并与回调中的`sign`进行比较[^3]。 示例代码如下: ```go package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "sort" ) func generateWeChatSign(params map[string]string, apiKey string) string { var keys []string for k := range params { if k != "sign" { keys = append(keys, k) } } sort.Strings(keys) var strToSign string for _, k := range keys { strToSign += fmt.Sprintf("%s=%s&", k, params[k]) } strToSign += "key=" + apiKey h := hmac.New(sha256.New, []byte(apiKey)) h.Write([]byte(strToSign)) return hex.EncodeToString(h.Sum(nil)) } ``` ### 注意事项 - **签名字段扩展性** 微信支付接口可能会新增字段,因此在验证签名时必须支持未知字段的存在,仅排除`sign`字段即可[^4]。 - **签名错误排查** 如果出现签名不一致的情况,应检查参数是否遗漏、拼接顺序是否正确、签名算法是否匹配(如MD5或SHA256),以及商户私钥是否正确无误[^5]。 - **安全性建议** 商户私钥绝对不能泄露,尽量避免硬编码在源码中,推荐通过配置中心或环境变量读取[^2]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值