微信支付(一):小程序支付(go+gin+内网穿透)

news2024/11/24 2:49:19

 一、前置条件

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/723457.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu安装:显卡驱动、CUDA、Anaconda

Ubuntu安装:显卡驱动、CUDA、Anaconda 摘要1.安装NVIDIA显卡驱动2.安装CUDA3.安装Anaconda Windows环境安装CUDA和Pytorch见:Pytorch入门:3.安装 环境:x86_64 Linux ubuntu18 4.150.0-20-generic 摘要 本篇博客对Ubuntu系统安装…

#消防知识#自动灭火系统是什么?

自动灭火系统是指能够在发生火灾时自动检测、控制和扑灭火灾的系统,包括自动喷水灭火系统、气体灭火系统、干粉灭火系统、气溶胶灭火系统等。不同的自动灭火系统有不同的组成部件、工作原理和适用范围,以下是一些简要的介绍:• 自动喷水灭火系…

汽车远程升级(OTA)定义与技术体系

1.汽车OTA定义 1.1. OTA概述 OTA(Over-the-air technology)是一种通过无线方式而不是使用电缆或其他本地连接进行数据传输的远程升级。能够实现对现有性能/功能的优化、新功能推送等。OTA技术最早应用于PC,而后在手机上普及,终结…

Go语言程序设计(二)常量、变量、布尔类型与运算符

一、常量、变量与命名规则 常量使用关键字const声明;变量可以使用关键字var声明,也可以使用快捷变量声明语法。Go语言可以自动推断出所声明变量的类型,但是如果需要显式指定其类型也是合法的,比如声明一种与Go语言的常规推断不同的…

途乐证券|沪指缩量跌0.69%,汽车等板块走弱,稀土概念逆市活跃

6日早盘,三大股指股指盘中震动回落,科创50指数逆市上扬;两市半日成交超5000亿元,北向资金小幅净流出。 到午间收盘,沪指跌0.53%报3205.97点,深成指跌0.39%,创业板指跌0.61%,科创50指…

新版 Alist + RaiDrive 挂载阿里云盘 Open 到本地,实现网盘本地化

新版 Alist RaiDrive 挂载阿里云盘 Open 到本地,实现网盘本地化 1. 下载 Alist 和 RaiDrive2. 配置 Alist3. 填写挂载路径和刷新令牌4. 获取刷新令牌5. 使用 RaiDrive 挂载 1. 下载 Alist 和 RaiDrive 下载地址:Alist RaiDrive 安装 提取码: qu38 Al…

XXX SAP系统中支持XX动力替代件功能(投稿数字化月报三)

XXX集团、XX动力、XXX汽车IT组成替代件开发小组,快速高效完成XX动力替代件业务需求。提供从XX动力的PLM系统中开发新增含有替代件功能的BOM物料清单、在XXX汽车SAP系统中启用替代件功能、再到现场替换件业务执行的全套解决方案。 在BOM上体现出物料的替换关系&#…

汽车电子—制作DBC文件

文章目录 一、前期准备二、新建DBC文件三、添加报文(Msg)四、添加信号4.1 大小端模式4.2 创建数值表4.3 添加信号 五、关联信号和报文六、设置报文发送类型和周期七、编辑通信矩阵 一、前期准备 首先需要安装制作DBC文件的软件,这里使用的是…

计算机网络概述(二)

计算机网络的定义 计算机网络并没有一个统一的定义,不同阶段是有不同的定义。 最简单的定义:计算机网络是一些互联的,自治的计算机集合。互联:指计算机之间可以通过有线或无线的方式进行数据通信;自治指的是独…

MySQL_01删除主键索引

文章目录 添加主键索引删除主键索引 添加主键索引 alter table 表名 add primary key(字段名)删除主键索引 -- 如果这个主键是自增的,先取消自增长 alter table 表名 modify 字段 int; alter table 表名 drop primary key;

消息队列 原理剖析

什么是消息队列? ​​消息队列是一种用来存储消息的队列。 消息队列能用来做什么? 1、消息或任务的延迟处理。 2、在复杂的业务场景下给系统减压。 3、提示用户体验感,增强用户体验。 4、敏感操作高安全环境处理。 场景举例1&#xff1a…

途乐证券|股票低开好还是高开好?股票低开高走再回落什么意思?

对于一向不涨的股票,出资者常常感到困惑和不安。那么一向不涨的股票要留吗?什么股票更简单上涨?为我们预备了相关内容,以供参阅。 一向不涨的股票要留吗? 一向不涨的股票要不要留没有一个绝对的答案,出资者…

Vue2.0-3.0 入门到实战 - 2 初始及插件安装

1 Vue 指令 v- 前缀的 特殊标签属性 1 v-html"表达式" 可以解析 文本html 类似 innerHtml 2 v-show 与 v-if v-show"表达式" true 表示 显示 false 标识隐藏 v-if"表达式" true 表示 显示 false 标识隐藏 区别 当v-show 的值…

青岛大学_王卓老师【数据结构与算法】Week04_05_双向链表的删除_学习笔记

本文是个人学习笔记,素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享,另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权,请留言作删文处理。 课程视频链接: 数据结构与算法基础–…

【CMake】构建类型详细解读

1. CMAKE_BUILD_TYPE官方资料 CMake可以配置构建类型,例如:Debug、Release等,控制生成构建系统使用的配置变量 是 CMAKE_BUILD_TYPE 。该变量默认为空,CMake识别的值为:Debug:用于在没有优化的情况下,使用带…

音频格式怎么转换成WAV?分享这三个WAV转换器!

随着数字时代的进步与发展,音频格式也变得越来越多样化。人们为了满足自己的需求,开始使用各种音频格式来存储和播放音乐。然而,不同的音频格式和不同的播放器之间常常存在兼容性问题,这就需要我们进行音频格式转换,以…

Spark SQL、DataFrame、DataSet是什么

在很多情况下,开发人员并不了解Scala语言,也不了解Spark常用的API,但又非常想要使用Spark框架提供的强大的数据分析能力。Spark的开发工程师们考虑到了这个问题,于是利用SQL语言的语法简洁、学习门槛低以及在编程语言中普及程度和…

开放式耳机推荐,盘点几款好用的开放式耳机

一款好的开放式蓝牙耳机不仅可以让我们缓解疲劳,还能更有动力,特别是音质的表现,如果一款开放式耳机的音质表现不好,那这款耳机也就没有多大意义了,还有就是佩戴舒适性,所以选择一款好的开放式蓝牙耳机也很…

vs2015调试时无法显示QT变量值

问题描述: vs2015调试时无法显示 QT变量值,只能显示地址,导致想要查看变量值的时候,只能想办法打印出来,非常麻烦。如下图: 问题解决: 调试 - 选项 - 调试 - 常规 - 去掉 使用本机兼容性模式&am…

压测性能调优之gateway网关

1、 压测资源和场景 (1)14个接口同时压测5000并发; (2)服务资源:采用k8s部署,总共25台8核64G阿里云机器,node节点个数22个,master3个,15个网关实例&#xf…