微信支付(一):小程序支付(Go+Gin+内网穿透+支付回调处理)

 一、前置条件

(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)支付回调方法代码:回调后写入队列,异步处理

(4)注意:如果微信支付时候,若用户使用了平台赠送的现金券之类的优惠【例如:首次绑卡,赠送10元现金券,订单那50元,用户支付40元,券10元;商家实收是50元;但是微信支付回调的时候,使用优惠券数据是单独存放的,需要累计上这个钱】

result.PromotionDetail[i].Amount,具体看代码
//WxPayNotify 解析微信回调请求的参数到 V3NotifyReq 结构体
func WxPayNotify(c *gin.Context) {
	fmt.Println("--------------------- WxPayNotify START ---------------------")
	// c.Request 是 gin 框架的写法
	notifyReq, err := wechat.V3ParseNotify(c.Request)
	if err != nil {
		fmt.Println("------ WxPayNotify V3ParseNotify ERR ------", err.Error())
		c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.FAIL, Message: "回调内容异常"})
		return
	}
	// 获取微信平台证书
	wxClient := lib.GetWechatClientV3()
	// 验证异步通知的签名
	err = notifyReq.VerifySignByPK(wxClient.WxPublicKey())
	if err != nil {
		fmt.Println("------ WxPayNotify VerifySignByPKMap ERR ------", err.Error())
		c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.FAIL, Message: "内容验证失败"})
		return
	}
	// 普通支付通知解密
	result, rErr := notifyReq.DecryptCipherText(config.GlobalConf.WxApiV3Key)
	if rErr != nil {
		fmt.Println("------ WxPayNotify DecryptCipherText Error ------", rErr.Error())
		c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.FAIL, Message: "内容解密失败"})
		return
	}
	if result != nil && result.TradeState == "SUCCESS" {
		fmt.Println("------ WxPayNotify PushMessToPayQueue START 【" + result.OutTradeNo + "】------")
		var wxReq = make(map[string]interface{})
		promotionAmount := 0
		for i := range result.PromotionDetail {
			promotionAmount += result.PromotionDetail[i].Amount
		}
		fmt.Println(fmt.Sprintf("------ WxPayNotify 优惠券总额 promotionAmount 【%d】------", promotionAmount))
		//商户订单号:商户系统内部订单号
		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 + promotionAmount
		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)
		fmt.Println("------ WxPayNotify PushMessToPayQueue END 【" + result.OutTradeNo + "】------")
	}

	/*var wxReq = make(map[string]interface{})
	//商户订单号:商户系统内部订单号
	wxReq["pay_no"] = "PAY22112815152611300011"
	//微信支付订单号:微信支付系统生成的订单号。
	wxReq["trade_no"] = "2022061322001402060"
	//与支付宝同步
	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: "成功"})
	fmt.Println("--------------------- WxPayNotify END ---------------------")
	return
}

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
基于golang gin微信点餐小程序的设计与实现需要考虑以下几个方面: 1. 架构设计:可以使用MVC(Model-View-Controller)架构,将代码按功能模块划分成不同的包,便于管理和维护。 2. 数据库设计:根据需求,设计适当的数据库结构,存储菜单信息、用户信息、订单信息等。可以使用关系型数据库如MySQL或非关系型数据库如MongoDB。 3. 用户认证与授权:使用微信小程序的登录功能进行用户认证,获取用户的OpenID等信息。为了保护用户数据安全,可以使用JWT(JSON Web Token)生成访问令牌,并设置权限控制,确保只有授权的用户能够访问相关接口。 4. 页面设计与交互:根据需求设计小程序的首页、菜单页、购物车、订单页等页面,合理安排页面布局和交互逻辑。可以使用小程序自带的模板或自定义组件进行页面开发。 5. 菜单管理:实现菜单的增删改查功能,包括菜品的图片、名称、价格、描述等信息。为了提高用户体验,可以增加搜索和排序等功能。 6. 购物车与订单管理:用户选择菜品后,将菜品添加到购物车,并生成相应的订单。可以实现购物车的增删改查功能,以及订单的支付、取消等功能。 7. 接口设计与实现:根据需求设计合适的API接口,包括菜单管理接口、用户认证接口、购物车接口、订单接口等。使用golanggin框架实现这些接口,并进行参数校验、错误处理等。 8. 部署与测试:将小程序部署到服务器上,并进行测试,确保功能正常运行。可以使用Postman等工具对API接口进行测试,模拟用户操作验证功能是否正确。 基于以上几个方面的设计与实现,可以打造一个基于golang gin微信点餐小程序,实现用户点餐、下单和支付的功能,提供便捷的餐饮服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值