reactjs +golang 微信支付坑坑坑

本贴最后更新于 2574 天前,其中的信息可能已经东海扬尘

P81023161420jpg

最近在做支付充值这一块,用的微信支付,发现坑点真不少。由于无 golang 的 sdk 于是拿着 github 上的别人写好的改了改,最终也还是圆满完成了支付这一块。

主要场景:微信支付-H5-微信内发起支付

主要内容:微信网页开发 >jssdk> 微信商户

开发语言:reactjs+golang

大概的主体思路如下每一步的操作都很重要
微信网页开发配置:
1、在微信后台配置域名(目的:获取 openid)
2、jssdk 域名的配置(目的:前端请求的配置)
3、商户后台的域名配置(目的:支付的配置)

主要配置就以上这些(应该没遗漏)
主要文档:网页授权的文档,jssdk wxconfig wxpay 的文档,微信商户支付文档

坑 1、微信授权 redrect_uri 错误(此处偷懒可以用别人的):自己写则仔细检查各种参数以及微信后台的配置即可,总会成功的
示例授权代码:

package controllers

import (
	"net/url"

	"charge/models"

	"github.com/astaxie/beego"
	mpoauth2 "gopkg.in/chanxuehong/wechat.v2/mp/oauth2"
	oauth "gopkg.in/chanxuehong/wechat.v2/oauth2"
	"gopkg.in/chanxuehong/wechat.v2/open/oauth2"
)

type Common struct {
	beego.Controller
	UserID uint
}


type WebController struct {
	Common
}

func (this *WebController) Prepare() {
	if !UserInterceptor(&this.Common) {
		WechatLogin(&this.Common)
	}
}

var oauth2Endpoint oauth.Endpoint = mpoauth2.NewEndpoint(Appid, AppSecret)

func WechatLogin(this *Common) {

	code := this.GetString("code")

	//第一次请求进来时候
	if code == "" {
		this.GoAuth()
		this.StopRun()
		return
	}
	client := oauth.Client{Endpoint: oauth2Endpoint}

	if token, err := client.ExchangeToken(code); err != nil {
		beego.Warning("exchange token error:", err)
		this.GoAuth()
		this.StopRun()
		return
	} else {
		if info, err := oauth2.GetUserInfo(token.AccessToken, token.OpenId, "", nil); err != nil {
			beego.Warning("get userinfo error:", err)
			this.GoAuth()
			this.StopRun()
			return
		} else {
			//保存用户信息到数据库
			var user models.User
			if models.DB.Where("openid =?", info.OpenId).First(&user).RecordNotFound() {

				user.Openid = info.OpenId
				user.Nickname = info.Nickname
				if err := models.DB.Create(&user).Error; err != nil {
					beego.Error("update user error:", err)
					this.StopRun()
					return
				}
				this.UserID = user.ID
				this.SetSession("userinfo", user)
			} else {
				if err := models.DB.Model(&user).Updates(map[string]interface{}{"Nickname": info.Nickname,}).Error; err != nil {
					beego.Error("user update error:", err)
					this.StopRun()
					return
				}
				this.UserID = user.ID
				this.SetSession("userinfo", user)
			}
		}

	}
}

func (this *Common) GoAuth() {
	//此处values
	uri, _ := url.Parse(beego.AppConfig.String("domain") + this.Ctx.Input.URI())

	values := uri.Query()
	values.Del("code")
	path := mpoauth2.AuthCodeURL(Appid, beego.AppConfig.String("domain")+
		this.Ctx.Input.URL()+
		"?"+ values.Encode(),
		"snsapi_userinfo", this.GetString("token"))
	this.Redirect(path, 302)
	this.StopRun()
	return
}

func UserInterceptor(ctr *Common) bool {
	if userinfo := ctr.GetSession("userinfo"); userinfo != nil {
		user := userinfo.(models.User)
		if err := models.DB.Where("id=? ", user.ID).First(&user).Error; err != nil {
			beego.Warning("user read error:", err)
			return false
		}

		ctr.UserID = user.ID
		return true
	}

	return false

}

//@router /* [*]
func (this *WebController) Index() {

	this.TplName = "react/index.html"
}

//@router /MP_verify_zpcrQi7YFTe5pSaV.txt [*]
func (this *WebController) MP() {
	this.TplName = "MP_verify_zpcrQi7YFTe5pSaV.html"
}


1、请求 ------> 服务器---302-----> 微信服务器(用户同意)

2、微信服务器--code--> 服务器

3、服务器----code-> 微信服务器----(用户信息和 token)------服务器

坑 2、jssdk 配置 wx.config 和 wx.chooseWXPay
reactjs 的微信 jssdk weixin-js-sdk

config 配置:react 获取当前路径我是使用 window 然后 split 域名,去掉默认的#号是使用 browserHistory 而不是用 hash 路由 我的每次 wx.chooseWXPay 请求之前都是调用了 wx.config config
关于调试:需线上配置好的环境在微信开发者工具中调试
关于签名失败:仔细检查参数

[!wx.config 基于微信后台域名的配置]

前端代码:

_getInitialState() {
        let url = window.location.href;
        let urlArr = url.split("xxxx");
        let that = this;
        let pathname = urlArr[1];
        window.$http.post('/api/jssdk', qs.stringify({path: pathname})).then(res => {
            if (res.status === 10000) {
                that.setState({
                    openid: res.openid
                });
                wx.config({
                    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                    appId: res.appid, // 必填,公众号的唯一标识
                    timestamp: res.timestamp, // 必填,生成签名的时间戳
                    nonceStr: res.noncestr, // 必填,生成签名的随机串
                    signature: res.signature,// 必填,签名
                    jsApiList: ["chooseWXPay", "onMenuShareTimeline", "addCard"], // 必填,需要使用的JS接口列表
                    success: function (res) {
                    }


                });
            }
        }).catch(err => {
        });

    }

后端 jssdk 接口

//@router /api/jssdk [*]
func (this *WebController) JSSDK() {

	path := this.GetString("path")


	noncestr := string(utils.Krand(16, 3))
	timestamp := time.Now().Unix()
	var user models.User
    
    //由于已经微信授权将openid写入数据库了
	if err := models.DB.Where("id = ?", this.UserID).First(&user); err != nil {
		beego.Warning(err)
	}
	//"gopkg.in/chanxuehong/wechat.v2/mp/jssdk"
	signature := jssdk.WXConfigSign(JSTICKET, noncestr, strconv.FormatInt(timestamp, 10), 你的域名+path)


	data := make(map[string]interface{})
	data["appid"] = Appid
	data["noncestr"] = noncestr
	data["timestamp"] = timestamp
	data["signature"] = signature
	data["status"] = 10000
	data["openid"] = user.Openid
	this.Data["json"] = data
	this.ServeJSON()
	return
}

然后预下单
将 openid 商品信息提交到服务端,服务端通过预下单接口下单(此处其实是模拟商户向微信请求此时商户的的参数包括)

//向商户下单[github.com/objcoding/wxpay]
func PostPreOrder(openid string, tradeNo string, amount int64, ip string, category int) (prepayId string, paySign string, isOk bool, err error) {
	account := wxpay.NewAccount(Appid, Mchid, ApiKey, false)
	client := wxpay.NewClient(account)

	bodyString := ""

	if category == 1 {
		bodyString = "callpay"
	} else {
		bodyString = "fuelpay"
	}
	// 设置http请求超时时间
	client.SetHttpConnectTimeoutMs(2000)

	// 设置http读取信息流超时时间
	client.SetHttpReadTimeoutMs(1000)
	client.SetSignType("MD5")
	params := make(wxpay.Params)

	fmt.Println("body:", bodyString)
	fmt.Println("appid:", Appid)
	fmt.Println("out_trade_no:", tradeNo)

	params.SetString("body", bodyString).
		SetString("appid", Appid).
		SetString("out_trade_no", tradeNo).
		SetInt64("total_fee", amount).
		SetString("spbill_create_ip", ip).
		SetString("notify_url", notifyUrl).
		SetString("trade_type", tradeType).
		SetString("openid", openid)
	/*
		beego.Error("-----------request param------------")

		for key, value := range params {
			beego.Error("key:", key, " value:", value)
		}
	*/
	returnParams, err := client.UnifiedOrder(params)

	if err != nil {
		log.Println(err)
		return "", "", false, err
	}
	/*
	beego.Error("-----------response param------------")
	for key, value := range returnParams {
		beego.Error("key:", key, " value:", value)
	}
	*/
	returnCode, ok := returnParams["return_code"]
	resultCode, ok := returnParams["result_code"]
	if returnCode == "SUCCESS" && resultCode == "SUCCESS" && ok {
		paySign, _ = returnParams["sign"]
		prepayId, _ := returnParams["prepay_id"]
		return prepayId, paySign, true, err
	}
	return "", "", false, err
}

上面返回了预下单的 preid 和 paysign(这是错误的签名) 后面我拿出来重新改写了

func (this *OrderController)  test(){
    noncestr := utils.Str2Md5(time.Now().Format("20060102150405"))
	timestamp := time.Now().Unix()
    
    //来自上面PostPreOrder()返回的
	paySign = Sign(prepayId, noncestr, strconv.FormatInt(timestamp, 10))
	/*
	
	rst["package"] = "prepay_id=" + prepayId
	rst["paySign"] = paySign
	rst["nonceStr"] = noncestr
	rst["timeStamp"] = strconv.FormatInt(timestamp, 10)
	rst["appId"] = Appid
	rst["status"] = 10000
	rst["tradeNo"] = tradeNo
	this.Data["json"] = rst
	this.ServeJSON()

}

/支付的签名函数 (包内的签名函数的改写)
func Sign(prepayId string, noncestr string, timestamp string) string {
	params := make(wxpay.Params)
	params.SetString("package", "prepay_id="+prepayId).
		SetString("nonceStr", noncestr).
		SetString("timeStamp", timestamp).
		SetString("appId", Appid).
		SetString("signType", "MD5")
	var keys = make([]string, 0, len(params))
	for k := range params {
		if k != "sign" { // 排除sign字段
			keys = append(keys, k)
		}
	}

	sort.Strings(keys)

	//创建字符缓冲
	var buf bytes.Buffer
	for _, k := range keys {
		if len(params.GetString(k)) > 0 {
			buf.WriteString(k)
			buf.WriteString(`=`)
			buf.WriteString(params.GetString(k))
			buf.WriteString(`&`)
		}
	}
	// 加入apiKey作加密密钥
	buf.WriteString(`key=`)
	buf.WriteString(ApiKey)

	var (
		dataMd5 [16]byte
		str     string
	)
	dataMd5 = md5.Sum(buf.Bytes())
	str = hex.EncodeToString(dataMd5[:])
	return strings.ToUpper(str)
}

返回给前端 6 个重要参数 其中 5 个是调用 wx.chooseWXPay 的(这是后端给前端的,也有一些写法是前端把参数准备好然后再请求)
此处注意参数 key 和 value 的准确性(比如大小写,值的类型等)

在以上基础上再调用 wx.chooseWXPay

_preOrder = () => {

        let that = this;
        let itemId = that.state.itemId;
        let mobile = that.state.mobile;
        let openid = that.state.openid;
        let category = that.state.category;

        mobile = mobile.replace(/\s/ig, '');
        //预下单
        window.$http.post('/api/order/pre', qs.stringify({
            mobile: mobile,
            id: itemId,
            category: category,
            openid: openid
        })).then(res => {
            //回调获得支付参数(也有是在前端将参数准备好的)
            let appId = res.appId;
            let timeStamp = res.timeStamp;
            let nonceStr = res.nonceStr;
            let packages = res.package;
            let paySign = res.paySign;
            that.setState({
                tradeNo: res.tradeNo
            });
            //发起支付[此处注意这个包帮你封装了一层 timestamp 实际微信文档为timeStamp]
            wx.chooseWXPay({
                appId: appId,
                timestamp: timeStamp.toString(),
                nonceStr: nonceStr,
                package: packages,
                signType: 'MD5',
                paySign: paySign,
                success: function (res) {
                    //完成后修改订单状态
                    that._paySuccess();
                },
            }).catch(err => {
                console.log("error")
            });

            if (res.status === 10001) {
                console.log("下单失败")
            }
        }).catch(err => {

        });
    };

总结:整个逻辑很简单但是一个人前后端都写的话,对于微信这一块不熟的话写起来就会犯迷糊,忘记了参数,类型等等等。相比来说支付宝的就简单很多,加油

  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    502 引用 • 1397 回帖 • 241 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    248 引用 • 1342 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
xhaoxiong
站在巨人的肩膀上学习与创新

推荐标签 标签

  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    76 引用 • 1746 回帖 • 10 关注
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    4 引用 • 16 回帖 • 198 关注
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 548 关注
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 152 关注
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    167 引用 • 408 回帖 • 494 关注
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 475 关注
  • 大疆创新

    深圳市大疆创新科技有限公司(DJI-Innovations,简称 DJI),成立于 2006 年,是全球领先的无人飞行器控制系统及无人机解决方案的研发和生产商,客户遍布全球 100 多个国家。通过持续的创新,大疆致力于为无人机工业、行业用户以及专业航拍应用提供性能最强、体验最佳的革命性智能飞控产品和解决方案。

    2 引用 • 14 回帖
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 64 关注
  • 倾城之链
    23 引用 • 66 回帖 • 188 关注
  • AWS
    11 引用 • 28 回帖 • 1 关注
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 646 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 847 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 443 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖
  • Word
    13 引用 • 41 回帖 • 1 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    85 引用 • 414 回帖
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    60 引用 • 22 回帖
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 412 关注
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖 • 7 关注
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 572 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    78 引用 • 37 回帖
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    79 引用 • 431 回帖 • 1 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 1 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    39 引用 • 170 回帖
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 623 关注
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    91 引用 • 59 回帖 • 1 关注