Bootstrap

微信小程序支付

系列文章目录

支付

微信支付

微信小程序支付



前言

现在电商平台这么发达,我们经常在网上购物,能选用的支付聚道大概有以下三种。

在这里插入图片描述
第一种是微信支付,我觉得在线下环境,用微信支付是最多的。因为手机长时间运行微信,所以掏出手机,直接拿微信就扫码支付了。虽然商家分别提供了微信和支付宝的付款码,但是习惯上,还是喜欢用微信扫码付款。另外,发红包、抢红包,以及微信转账,也都用上了微信支付接口。微信支付的优点是简单易用,但是缺点也很明显,就是专业化不足。并没有把支付跟金融产品、信用借贷、以及生活缴费联系在一起。

在这里插入图片描述
这方面支付宝做的更好,可以在支付宝上面缴纳各种费用,甚至说查询医保社保也都没问题。去医院之前,可以在支付宝上面挂号。觉得父母年纪大了,可以在支付宝上面购买健康体检服务。至于说金融和保险产品,支付宝上面就更多了。而且支付宝有个杀手级的伙伴,那就是淘宝,在淘宝上面优先推荐的是花呗渠道,然后才是支付宝上面绑定的各种银行卡渠道。每年被淘宝分出去的支付流量,就让微信看的眼红。所以中国主流的支付渠道还是支付宝平台。
在这里插入图片描述
最后我要说的是银联的这个平台。以前银联的付款渠道门槛很高,买家和卖家用起来都不方便。比如说线下渠道,商家必须要安装POS机,然后还要被银联扣除手续费。线上付款的时候,还要在电脑上安装IE浏览器才支持的网银插件。反正不管怎么说,买家和卖家都不愿意用银联渠道。这几年,银联也意识到自己支付渠道变得边缘化。于是就弄出来个云闪付功能,拼命的促销优惠,建立支付场景。

首先支付宝的份额是最大的,市场占有率是54%。光是淘宝带给支付宝的流量就十分巨大。所以支付宝稳坐支付头把交椅没什么可怀疑的。排在第二位的是微信支付,实战份额是40%,毕竟先下小额支付是微信支付的主战场。最后来看一下银联支付吧,连云闪付都算上,银联的市场份额只有0.4%,京东支付都能做到0.7%的份额,看来银联想要赶超其他支付渠道,真的还有一段路要走。


一、微信小程序支付

1.微信支付种类说明

1.1.1 付款码支付场景

在线下消费的时候,经常要出示付款码,商家扫描了你的付款码就自动完成了消费,根本不需要我们输入密码,可见这种支付模式是免密的。商家想要实现扫描付款码收款,就必须先采购安装扫码设备,既兼容支付宝的付款码,又兼容微信的付款码。这种扫码设备在淘宝上面的售价大概是2~300块钱,安装和配置都非常简单。我们在饭店、电影院、蛋糕店,还有大型超市都见过这种类似的设备,也有手持的扫码器。
在这里插入图片描述
另外,我们乘公交车的时候,也可以出示付款码了。
在这里插入图片描述

1.1.2 付款码和收款码的区别

 用付款码收钱的好处是,可以让支付过程接入商户的软件系统。而用收款码收钱,是没办法接入商户本地的软件系统的。比如说去饭店请客吃饭,然后在结账的时候,收银员在本地的管理软件上,核算顾客的消费金额和明细,然后让用户付款。这时候,掏出手机出示付款码,然后商家用扫码器扫描付款码。这个付款码会传递给商户的软件系统,然后商户的软件系统向微信平台发出扣款请求。微信平台收到请求之后,就立即执行扣款。然后把支付结果发送给商户的软件系统,商户系统接收到付款成功的通知以后,立即把这次消费订单和支付订单关联在一起,保存在商户本地的管理软件中。微信平台除了把支付结果发送给商户平台之外,还要把支付结果发送给顾客的微信。这样顾客就知道自己付款了多少钱。
 另外话说回来,采用付款码扣款的好处是可以把消费订单和支付订单关联在一起,将来发生纠纷的时候也非常好解决。比方说有位顾客在饭店请客吃饭,晚上回到家出现了轻微食物中毒的症状,于是第二天就找到饭店,说昨天在这里吃的某一道菜,原料不新鲜,结果回到家上吐下泻,去医院挂了吊瓶才有好转。于是今天来饭店跟店家索赔。因为饭店的收银员采用的是扫描付款收钱,所以在商家的本地的管理系统中,能查到付款信息,而且还能查到,这笔付款对应的是具体消费明细。顾客点了什么菜,喝了什么酒,最后结账还打了9折。商家核对之后,发现顾客确实昨天就餐的时候点过某道菜,看来顾客不是来讹人的,所以双方就可以协商解决。

如果商家没有采用扫描收款码结账,用户在手机上面扫描商家的收款码进行付款,这笔付款会记录在微信商户平台,但是这笔付款究竟跟哪个消费订单想关联,我们并不知道。如果顾客找回来,说自己昨天吃的某道菜不新鲜了,导致食品中毒,店家得赔偿医疗费用。那么作为店家自己也说不清楚。所以现在很多商家,宁可花钱采购扫码器,也要使用扫描付款码收费,就是为了能记录每笔支付对应的详细消费情况。

1.1.3 JSAPI支付场景

JSAPI听起来像是在网页面调用的微信支付接口,是不是这样子呢?你别说,还真是网页上面调用的支付接口,但是有个前提要求,必须得是微信内置的浏览器才可以。

微信的社交属性毋庸置疑,平时有亲朋好友忍不住,总给发一些分享的链接。估计大部分都是心灵鸡汤类的,也有少数人给发送商品链接。如果点开这个商品链接,觉得商品还不错的话,想要下单支付,这个时候呢,电商网站就需要调用微信的JSAPI支付接口,为用户提供微信支付功能。
在这里插入图片描述

1.1.4 H5支付场景

刚才看到的是用户在微信里边打开电商网站下单支付,要使用JSAPI支付接口。如果用户是在手机内置的浏览器里边打开电商网站,选好商品下单支付,这个时候呢,电商网站就得调用H5支付接口,为用户提供微信支付功能。比如说大家看这幅截图,这是在手机浏览器上面打开的京东商城网站。在网页上面可以正常的购物下单,只要付款的时候选择了微信支付,那么京东商城就得调用微信的H5支付接口。
在这里插入图片描述
也就是说电商网站的页面,必须要判断用户的浏览器环境是什么,这样它才能判断应该调用微信的哪一种支付接口。
如果是在手机浏览器里面选择用微信支付,那么电商网站必须要调用H5支付接口。

1.1.5 Native支付

用户在PC浏览器上面下单,然后选择微信支付,这时候电商网站就的调用微信平台的Native支付接口,才能满足用户微信支付的要求。请看这个截图。这是在京东商城上面下的订单,然后选择得微信支付,于是页面上就出现了收款码,我们只需要用微信扫码并且付款就行了。
在这里插入图片描述
还是相同的套路,在网站上选择微信付款的时候,电商网站先要检测浏览器环境,如果是PC浏览器,这时候就应该调用微信平台的Native支付接口。

1.1.6 小程序支付场景

微信小程序确实挺方便的,定外卖的时候,不用安装APP,直接找到美团外卖小程序,就可以选购爱吃的美食,然后下单支付了。或者说,去外地出差,想要订酒店。打开微信携程小程序,很容易就订到酒店了。类似的小程序支付场景太多太多了,我就不举例子了。
在这里插入图片描述
看到微信小程序支付的画面,这个界面我们非常熟悉。在小程序里面实现支付功能,并不困难,只要按照API的要求,传递参数提交数据,并且接收响应就行了。

1.1.7 APP支付场景

如果在拼多多上面想选购一个USB typec的扩展坞,在拼多多APP上面下单之后,如果选择的是微信支付,那么拼多多APP,就得调用微信支付的APP接口,才能完成微信支付。
在这里插入图片描述

1.1.8 刷脸支付

刷脸支付这种付款方式,不需要我们掏出手机,直接走到大屏幕前面,经过人脸识别之后,就可以付款了。这种方式让支付方式更加简单便捷了,但是商家的采购成本也上去了。就拿截图里面这种大型的刷脸设备,价格非常贵,在五六千元上下。
在这里插入图片描述
另外左侧的这种小型的刷脸设备,价格在1500到2000元之间,也不便宜。所以说,硬件成本制约着刷脸支付的普及,相信日后还是二维码首付款的方式的天下。你让一个出租车司机随身带着扫脸支付设备也并不现实,打印一个收款码贴在副驾驶的位置成本多低啊。

2. 开发的准备工作

安装小程序开发者工具 https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
注册微信公众平台账号 https://mp.weixin.qq.com/
注册账号的时候要注意几个事项:
1.必须选择小程序类型。
2.选择企业类型的账号,个人类型的账号是无法使用支付接口的。
3.完成注册之后,必须要完成企业认证,并且缴纳认证费用。
4.开通微信商户平台账号。
开发微信支付业务必备材料
1.小程序的AppID、AppSecret
2.商户平台的ID,数字证书和密钥

3.小程序支付接口原理

3.1.1 创建支付订单

使用微信支付,那么就要在微信平台生成支付订单,注意这个支付订单微信平台上面的,跟电商平台的购物订单不是一回事儿。微信平台要记录什么时候,哪个用户向哪个商户付款,这些信息必须要记录清楚,所以需要向微信平台提交支付订单。这幅时序图,讲的就是怎么生成支付订单的。这个支付订单不是小程序向微信平台申请创建的,而是由商户系统发出请求,让微信平台创建。为什么不是小程序来创建呢?在微信平台眼中,商户比微信用户更值得信任,这是因为想要开通微信支付,必须要通过企业认证,而微信用户是不需要实名认证的。所以由商户平台发起请求创建支付订单更加值得信任。另外创建支付订单需要提交很多数据,包括AppID、AppSecret、密钥、数字证书,还有商户号。因为小程序是安装在用户手机上面的,所以这些重要的材料不适合保存在小程序上面。既然这些重要的材料应该保存在商户系统上面,那么申请创建支付订单的请求,就应该由商户系统发出了。好了,下面咱们来仔细看一下,创建微信支付订单的流程。
在这里插入图片描述1.首先用户在小程序上面点击微信支付按钮。
2.小程序发起Ajax请求,告诉商户系统,你应该向微信平台申请创建支付订单了。
3.接下来商户系统先要提取小程序提交过来的数据,比如说OpenID是否有效,OpenId是用户拿微信登陆小程序产生的ID值。商户系统在数据库里面要记录这个OpenID值。发起付款的时候,商户系统必须要验证一下,小程序提交过来OpenId在数据库里面是不是存在。如果有人拿POSTMAN模拟小程序提交支付请求,胡乱编了一个OpenId,商户系统必须要能分辨出来。还有就是小程序提交过来的订单编号,商户系统也要验证订单的有效性。
4.商户系统要向微信平台发送生成支付订单的请求,并且上传跟支付相关的各种信息。比如说,发起付款的用户,微信的OpenID是什么?订单的金额是什么?人民币付款,还是美元付款?收款的商户ID是什么等等。
5.微信平台收到这些信息之后,核实之后没有问题,那么微信平台上面就会生成支付订单,并且把订单的信息返回给商户系统。
6.商户系统得到订单信息之后,还要对这些信息生成MD5数字签名。然后商户系统,会把支付订单的参数,返回给小程序。

3.1.2 用户付款

在这里插入图片描述
刚才说到了,小程序会拿到商户平台返回的支付参数。但是小程序这边也担心,毕竟订单是商户系统生成的,万一生成的支付金额,跟商品订单的金额对不上怎么办?说的难听一点,用户买了300块钱的商品,但是商户系统在微信平台上面创建了一个3000块钱的支付订单。小程序这边是不是得提防商户系统创建高额支付订单,所以还是得拿着支付订单号,到微信平台上面查询一下,究竟这个支付订单的金额是多少钱?
1.小程序把商户系统返回的支付参数,提交给微信平台。
2.微信平台确认小程序提交的支付参数没有问题,于是就把商户创建的支付订单信息返回给小程序,让用户确认。
3.这时候小程序就会弹出支付的金额和收款的商户,以及备注信息。
4.用户对支付金额没有异议,于是就输入支付密码或者扫脸,接下来小程序想微信平台发送确认支付的请求。
5.微信平台验证用户支付密码正确,就可以执行支付订单了。是从用户零钱里扣除,还是从银行卡里扣款,这个就是微信平台的事情了。
6.微信平台会把支付结果,分别发送给商户系统和小程序。然后用户和商户就都知道付款到底成功,还是失败了。

4. 微信账号登陆小程序

想要实现这个支付流程,首先必须要做的是,给小程序实现微信登陆的功能。首先由小程序发起请求给商户系统,让商户系统申请创建支付订单。想要判断到底是不是真实的用户发来的请求,只需要判断两样东西即可。一个是OpenID,另一个是Token字符串。用户在手机上用微信账号登陆小程序的时候,会产生一个唯一的OpenID值,商户系统会记录下这个OpenID值。如果商户系统接收到的请求里面没有OpenID值,或者OpenID值跟数据库里面的对不上,就说明这不是一个合法的用户。那么商户系统就不用理会这个请求。仅仅OpenID能核对上还不行,还要看看发起请求的用户是不是已经登陆了小程序。只有用户登陆小程序之后,才可以证明是本人下单支付,所以商户系统必须要判断用户到底有没有登陆小程序。因为使用开源框架renren-fast的后端项目,整合了Shiro和JWT技术,所以成功登陆小程序的用户,后端系统都会返回一个Token字符串。小程序每次发起请求的时候都要带上这个令牌字符串,告诉后端系统,我现在已经登陆了(后端系统也要验证这个Token字符串是否有效,以及过没过期)。

4.1 UNI-APP登陆接口

uni.login(OBJECT)

代码如下(示例):

uni.login({
    provider: 'weixin',
    success: function (loginRes) {
        console.log(loginRes.authResult);
    }
});

4.2 微信小程序接口

代码如下(示例):

uni.getUserInfo({
    success:function(res){
        console.log(res)
        let nickname=res.userInfo.nickName;
        let avatarUrl=res.userInfo.avatarUrl;
        uni.request({
            url:that.url.wx.login,
            method:"POST",
            data:{
                "code":code,
                "nickname":nickname,
                "photo":avatarUrl
            },
            success:function(res){
                console.log(resp);
                let token=resp.data.token;
                let expire=resp.data.expire;
                // uni.setStorageSync("token",token);
                // uni.setStorageSync("expire",expire);
                uni.switchTab({
                    url:"../index/index";
                })
            }
        })
    }
})

4.3 后端代码接口

代码如下(java语言示例):

配置文件
application:
     wxpay:
     app-id: wx4cb8e*********
     app-secret: 27b1f2997***************
     mch-id: 1526******
     key: qv9Kihy***********
     cert-path: C:/apiclient_cert.p12
     
WxLoginForm.java
Data
@ApiModel(value = "微信登录表单")
public class WxLoginForm {
     @ApiModelProperty(value = "临时登陆凭证")
     @NotBlank(message="临时登陆凭证不能为空")
     private String code;
     @ApiModelProperty(value = "昵称")
     @NotBlank(message="昵称不能为空")
     private String nickname;
     @ApiModelProperty(value = "头像URL")
     @NotBlank(message="头像URL不能为空")
     private String photo;
}

WxCtroller.java
@RestController
@RequestMapping("/app/wx")
@Api("微信业务接口")
public class WxController {
   @Value("${application.wxpay.app-id}")
    private String appId;

    @Value("${application.wxpay.app-secret}")
    private String appSecret;

    @Value("${application.wxpay.key}")
    private String key;

    @Value("${application.wxpay.mch-id}")
    private String mchId;

    @Autowired
    private UserService userService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private MyWXPayConfig myWXPayConfig;

    @PostMapping("login")
    @ApiOperation("登录")
    public R login(@RequestBody WxLoginForm form) {
        //表单校验
        ValidatorUtils.validateEntity(form);
        String url = "https://api.weixin.qq.com/sns/jscode2session";
        HashMap map = new HashMap();
        map.put("appid", appId);
        map.put("secret", appSecret);
        map.put("js_code", form.getCode());
        map.put("grant_type", "authorization_code");
        String response = HttpUtil.post(url, map);
        JSONObject json = JSONUtil.parseObj(response);
        String openId = json.getStr("openid");
        if (openId == null || openId.length() == 0) {
            return R.error("临时登陆凭证错误");
        }
        UserEntity user = new UserEntity();
        user.setOpenId(openId);
        QueryWrapper wrapper = new QueryWrapper(user);
        int count = userService.count(wrapper);
        if (count == 0) {
            user.setNickname(form.getNickname());
            user.setPhoto(form.getPhoto());
            user.setType(2);
            user.setCreateTime(new Date());
            userService.save(user);
        }
        user = new UserEntity();
        user.setOpenId(openId);
        wrapper = new QueryWrapper(user);
        user = userService.getOne(wrapper);
        long id = user.getUserId();

        String token = jwtUtils.generateToken(id);
        Map<String, Object> result = new HashMap<>();
        result.put("token", token);
        result.put("expire", jwtUtils.getExpire());

        return R.ok(result);
    }
 }  

代码如下(Go语言示例):

配置文件
[application]
wxpay.app-id=wx4cb8e*
wxpay.app-secret=27b1f2997*
wxpay.mch-id=1526*** wxpay.key=qv9Kihy
wxpay.cert-path=F:/apiclient_cert.p12
     
WxLoginForm.go
import "github.com/go-playground/validator/v10"

type WxLoginForm struct {
	Code string json:"code" validate:"required"
	Nickname string json:"nickname" validate:"required"
	Photo string json:"photo" validate:"required"
}

WxCtroller.go
type WxController struct {
	AppId string
	AppSecret string
	Key string
	MchId string
	UserSvc *UserService
	OrderSvc *OrderService
	JwtUtils *JwtUtils
	WxPayCfg *MyWXPayConfig
}

func (c *WxController) Login(ctx *gin.Context) {
	var form WxLoginForm
	if err := ctx.ShouldBindJSON(&form); err != nil {
	ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	return
	}

	// 使用结构体的 Validate 方法,进行表单校验
	if err := form.Validate(); err != nil {
	    ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	    return
	}

	url := "https://api.weixin.qq.com/sns/jscode2session"
	params := map[string]string{
	    "appid":      c.AppId,
	    "secret":     c.AppSecret,
	    "js_code":    form.Code,
	    "grant_type": "authorization_code",
	}

	response, err := HttpPost(url, params)
	if err != nil {
	    ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
	    return
	}

	json, err := ParseJSON(response)
	if err != nil {
	    ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
	    return
	}

	openId, ok := json["openid"].(string)
	if !ok || len(openId) == 0 {
	    ctx.JSON(http.StatusBadRequest, gin.H{"error": "临时登陆凭证错误"})
	    return
	}

	user := &UserEntity{OpenId: openId}
	if count, err := c.UserSvc.Count(user); err != nil {
	    ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
	    return
	} else if count == 0 {
	    user.Nickname = form.Nickname
	    user.Photo = form.Photo
	    user.Type = 2
	    user.CreateTime = time.Now()
	
	    if err := c.UserSvc.Save(user); err != nil {
	        ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
	        return
	    }
	}

	user = &UserEntity{OpenId: openId}
	if err := c.UserSvc.GetOne(user); err != nil {
	    ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
	    return
	}

	id := user.UserId

	token, err := c.JwtUtils.GenerateToken(id)
	if err != nil {
	    ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
	    return
	}

	result := gin.H{
	    "token":  token,
	    "expire": c.JwtUtils.GetExpire(),
	}
	ctx.JSON(http.StatusOK, result)
}

代码如下(c#语言示例):

配置文件
<configuration>
	  <appSettings>
	    <add key="wxpay:app-id" value="wx4cb8e*********"/>
	    <add key="wxpay:app-secret" value="27b1f2997***************"/>
	    <add key="wxpay:mch-id" value="1526******"/>
	    <add key="wxpay:key" value="qv9Kihy***********"/>
	  </appSettings>
	  <connectionStrings>
	    <add name="WxPayCert" connectionString="C:/apiclient_cert.p12" />
	  </connectionStrings>
</configuration>
     
WxLoginForm.cs
using System.ComponentModel.DataAnnotations;
public class WxLoginForm {
     [Required(ErrorMessage = "临时登陆凭证不能为空")]
     public string Code { get; set; }
     [Required(ErrorMessage = "昵称不能为空")]
     public string Nickname { get; set; }
     [Required(ErrorMessage = "头像URL不能为空")]
     public string Photo { get; set; }
}

WxCtroller.net
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace YourNamespace.Controllers
{
    [ApiController]
    [Route("app/wx")]
    public class WxController : ControllerBase
    {
        private readonly IConfiguration _configuration;
        private readonly IUserService _userService;
        private readonly IOrderService _orderService;
        private readonly IJwtUtils _jwtUtils;
        private readonly IMyWXPayConfig _myWXPayConfig;

        public WxController(IConfiguration configuration, IUserService userService,
            IOrderService orderService, IJwtUtils jwtUtils, IMyWXPayConfig myWXPayConfig)
        {
            _configuration = configuration;
            _userService = userService;
            _orderService = orderService;
            _jwtUtils = jwtUtils;
            _myWXPayConfig = myWXPayConfig;
        }

        [HttpPost("login")]
        public IActionResult Login([FromBody] WxLoginForm form)
        {
            // 表单校验
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            string appId = _configuration["application.wxpay.app-id"];
            string appSecret = _configuration["application.wxpay.app-secret"];
            string key = _configuration["application.wxpay.key"];
            string mchId = _configuration["application.wxpay.mch-id"];

            // 根据需要处理登录逻辑
            // ...

            return Ok(result);
        }
    }
}

5. 微信支付接口通用参数

5.1 接口说明

为了保证交易的安全,必须要使用HTTPS协议。
上传数据必须使用POST方式提交
提交的数据和返回的结果都是XML格式的,并且根节点叫做。
所有的数据必须采用UTF-8编码。

5.2 参数通用说明

交易的金额单位是分,而且不能有小数点,最低的支付金额为1元。
在小程序上使用微信支付,所以交易类型要写成JSAPI。

6. 创建支付订单的接口说明

生成支付订单的微信API接口,可以访问这个地址。里面有接口的详细说明。为了节省时间,提取了其中必须上传的一些参数。

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

6.1 请求参数

参考微信平台链接地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

  • appid参数是商户的公众号ID,也就是注册小程序账号时候得到的app-id。
  • mch_id参数是商户号,申请微信支付的时候,可以在微信商户平台的网站上面得到商户号。
  • nonce_str参数是随机字符串,这个可以用微信支付的SDK程序来生成。
  • sign参数是签名字符串,签名字符串,就是对上传的数据做MD5计算,得到的结果就是签名字符串了。SDK程序可以完成这个功能。对上传的数据做签名,一方面预防数据丢包,另外一方面放置数据在网络传输过程中被篡改。所以微信平台收到数据之后,重新计算一下数字签名。跟提交上来的数字签名比较一下就知道了。
  • body参数是商品的概要描述,这个内容可以随便写,是给支付订单做备注的,一般写的都是商品的名称。确认支付之前,小程序会拿着商户的支付参数,去微信平台搜索支付订单,然后用户手机上就会出现这个支付订单的信息了。其中就有订单的概要描述。这就是现在这个参数的用途
  • out_trade_no参数是商户订单号,你自己定义,或者用SDK程序生成,都是可以的。
  • total_fee参数是订单金额,整数类型,单位不是元,而是分。
  • spbill_create_ip参数是终端的IP地址,这里上传主机的IP地址即可,写什么IP地址,都不会影响到微信支付的。
  • notify_url参数是通知地址,这个参数非常重要。因为将来微信支付成功以后,微信平台会把支付成功的结果发送给项目,这里写的就是项目接收支付结果的URL路径。如果这个地址写错了,倒是不会影响到微信支付,只是在支付成功以后,微信平台会每隔几分钟发送一次通知请求。如果没有响应,发过若干次通知之后,微信平台就不会发送通知了。因为项目还没有发布,没有固定网址,所以写程序的时候,这个回调地址可以先随便写一个,等将来把项目部署在腾讯云上面,再把这个回调通知地址改成真实的网址即可。
  • trade_type参数是交易类型,固定写成JSAPI。
  • openid参数也就是微信用户登陆小程序时候,传给项目的参数。

6.2 响应结果

参考微信平台链接地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

6.3 错误码

参考微信平台链接地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

7. 创建支付订单代码实现

代码如下(java语言示例):

PayOrderForm.java
@Data
@ApiModel(value = "订单付款的表单")
public class PayOrderForm {
    @ApiModelProperty(value = "订单ID")
    @Min(1)
    private Integer orderId;

}

WxController中用于创建支付订单
@Login
@PostMapping("/microAppPayOrder")
@ApiOperation("小程序付款")
public R microAppPayOrder(@RequestBody PayOrderForm form, @RequestHeader HashMap header) {
    ValidatorUtils.validateEntity(form);
    String token = header.get("token").toString();
    Long userId = Long.parseLong(jwtUtils.getClaimByToken(token).getSubject());
    int orderId = form.getOrderId();
    UserEntity user = new UserEntity();
    user.setUserId(userId);
    QueryWrapper wrapper = new QueryWrapper(user);
    long count = userService.count(wrapper);
    if (count == 0) {
        return R.error("用户不存在");
    }
    String openId = userService.getOne(wrapper).getOpenId();

    OrderEntity order = new OrderEntity();
    order.setUserId(userId.intValue());
    order.setId(orderId);
    order.setStatus(1);
    wrapper = new QueryWrapper(order);
    count = orderService.count(wrapper);
    if (count == 0) {
    return R.error("不是有效的订单");
    }
    //验证购物券是否有效
    //验证团购活动是否有效

    order = new OrderEntity();
    order.setId(orderId);
    wrapper = new QueryWrapper(order);
    order = orderService.getOne(wrapper);
    //向微信平台发出请求,创建支付订单
    String amount = order.getAmount().multiply(new BigDecimal("100")).intValue() + "";

    try {
        WXPay wxPay = new WXPay(myWXPayConfig);
        HashMap map = new HashMap();
        map.put("nonce_str", WXPayUtil.generateNonceStr()); //随机字符串
        map.put("body", "订单备注");
        map.put("out_trade_no", order.getCode());
        map.put("total_fee", amount);
        map.put("spbill_create_ip", "127.0.0.1");
        map.put("notify_url", "https://127.0.0.1/test");
        map.put("trade_type", "JSAPI");
        map.put("openid", openId);
        Map<String, String> result = wxPay.unifiedOrder(map);
        String prepayId = result.get("prepay_id");
        System.out.println(prepayId);
        if (prepayId != null) {
            order.setPrepayId(prepayId);
            order.setPaymentType(1);
            UpdateWrapper updateWrapper = new UpdateWrapper();
            updateWrapper.eq("id", order.getId());
            orderService.update(order, updateWrapper);

            //生成数字签名
            map.clear();
            map.put("appId", appId);
            String timeStamp = new Date().getTime() + "";
            map.put("timeStamp", timeStamp);
            String nonceStr = WXPayUtil.generateNonceStr();
            map.put("nonceStr", nonceStr);
            map.put("package", "prepay_id=" + prepayId);
            map.put("signType", "MD5");
            String paySign = WXPayUtil.generateSignature(map, key);
            return R.ok().put("package", "prepay_id=" + prepayId)
                .put("timeStamp", timeStamp)
                .put("nonceStr", nonceStr)
                .put("paySign", paySign);
        }
        else {
            return R.error("支付订单创建失败");
        }
    }
    catch (Exception e) {
        e.printStackTrace();
        return R.error("微信支付模块故障");
    }
}

order.vue
uni.request({
    url: that.url.wx.microAppPayOrder,
    method: "POST",
    header: {
        "token": uni.getStorageSync("token")
    },
    data: {
        "orderId": id
    },
    success: function(res) {
        console.log(res);
        let timeStamp = res.data.timeStamp;
        let nonceStr = res.data.nonceStr;
        let pk = res.data.package;
        let paySign = res.data.paySign;
    }
})

8. 用户付款代码实现

uni.requestPayment(OBJECT)
参考微信平台链接: https://uniapp.dcloud.net.cn/api/plugins/payment.html#h5-payment

代码如下(示例):

// 仅作为示例,非真实参数信息。
uni.requestPayment({
    provider: 'wxpay',
    timeStamp: String(Date.now()),
    nonceStr: 'A1B2C3D4E5',
    package: 'prepay_id=wx20180101abcdefg',
    signType: 'MD5',
    paySign: '',
    success: function (res) {
        console.log('success:' + JSON.stringify(res));
    },
    fail: function (err) {
        console.log('fail:' + JSON.stringify(err));
    }
});

编写order.vue
uni.requestPayment({
    "timeStamp": timeStamp,
    "nonceStr": nonceStr,
    "package": pk,
    "signType": "MD5",
    "paySign": paySign,
    success: function() {
        uni.showToast({
            title: "支付成功"
        })
    },
    fail: function() {
        uni.showToast({
            title: "支付失败"
        })
    }
})

9.接收付款通知说明

参考微信平台链接地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

10.商户平台接受支付结果

写好了商户系统接收微信平台支付通知的WEB接口,通过模拟测试,这个接口接收到通知消息之后,真的能修改订单的状态。如果项目正式上线以后,那么创建微信支付订单的时候,就可以写这个WEB接口的网址,于是WEB接口就能收到微信平台的支付通知了。

代码如下(java语言示例):

@ApiOperation("接收消息通知")
@RequestMapping("/recieveMessage")
public void recieveMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {
    request.setCharacterEncoding("utf-8");
    Reader reader = request.getReader();
    BufferedReader buffer = new BufferedReader(reader);
    String line = buffer.readLine();
    StringBuffer temp = new StringBuffer();
    while (line != null) {
    temp.append(line);
        line = buffer.readLine();
    }
    buffer.close();
    reader.close();
    Map<String, String> map = WXPayUtil.xmlToMap(temp.toString());
    String resultCode = map.get("result_code");
    String returnCode = map.get("return_code");
    if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)) {
        String outTradeNo = map.get("out_trade_no");
        UpdateWrapper wrapper = new UpdateWrapper();
        wrapper.eq("code", outTradeNo);
        wrapper.set("status", 2);
        orderService.update(wrapper);
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/xml");
        Writer writer = response.getWriter();
        BufferedWriter bufferedWriter = new BufferedWriter(writer);
        bufferedWriter.write("<xml><return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]>		  </return_msg></xml>");
        bufferedWriter.close();
        writer.close();
    }
}

11.主动查询支付结果说明

即便把商户系统发布到外网,也不能保证一定会接收到微信平台发送过来的支付结果通知。比如说,微信平台出现技术故障,支付模块正常,但是消息模块出了问题,那么就会出现支付成功,但是消息发布出去。商户系统自然也就收不到支付结果的通知了。还有一种情况,那就是网络故障,无论哪一端出现了网络故障,商户系统都不能收到消息通知。所以在商户系统接收不到支付结果的时候,得想点别的办法,比如说让商户系统主动去查询支付的结果,到底成功了还是失败了?

下面来看一下,商户系统什么时候应该发起主动查询呢?难道还要写个定时程序吗?当然不需要。当小程序上面提示支付成功以后,那么小程序就像商户系统发出Ajax请求,告诉商户系统我已经收到付款成功的通知啦,你也去查询一下,这时候商户系统才需要发起主动查询。
在这里插入图片描述
在小程序上面的SUCCESS回调函能执行,就意味着支付成功了。在SUCCESS回调函数里面向商户系统发起Ajax请求即可。但是要仔细想一想商户系统接收到通知以后,它不能轻易的相信小程序的话,万一订单没支付,商户系统接收的是POSTMAN发过来的请求呢?要是轻易的相信用户付款成功,然后就修改订单状态。这就会出现,用户根本没付款,然后用POSTMAN发出一个付款成功的请求,于是订单就变成了已支付状态,然后商家就给用户发货了,你说商家是不是损失大了?所以商户平台接收到小程序的请求之后,他要拿着这个支付订单ID,到微信平台查询一下,是不是订单已经付款成功了。如果成功的话,那么就修改本地商品订单为已支付状态。

参考微信平台链接地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

12.主动查询支付结果

代码如下(java语言示例):

编写UpdateOrderStatusForm.java
@Data
@ApiModel(value = "更新订单状态的表单")
public class UpdateOrderStatusForm {
    @ApiModelProperty(value = "订单ID")
    @Min(1)
    private Integer orderId;
}

WxCtroller.java
@Login
@PostMapping("/updateOrderStatus")
@ApiOperation("更新商品订单状态")
public R updateOrderStatus(@RequestBody UpdateOrderStatusForm form, @RequestHeader HashMap header) {
    ValidatorUtils.validateEntity(form);
    String token = header.get("token").toString();
    int userId = Integer.parseInt(jwtUtils.getClaimByToken(token).getSubject());
    int orderId = form.getOrderId();
    OrderEntity orderEntity = new OrderEntity();
    orderEntity.setUserId(userId);
    orderEntity.setId(orderId);
    QueryWrapper wrapper = new QueryWrapper(orderEntity);
    int count = orderService.count(wrapper);
    if (count == 0) {
        return R.error("用户与订单不匹配");
    }
    orderEntity = orderService.getOne(wrapper);
    String code = orderEntity.getCode();
    HashMap map = new HashMap();
    map.put("appid", appId);
    map.put("mch_id", mchId);
    map.put("out_trade_no", code);
    map.put("nonce_str", WXPayUtil.generateNonceStr());
    try {
        String sign = WXPayUtil.generateSignature(map, key);
        map.put("sign", sign);
        WXPay wxPay = new WXPay(myWXPayConfig);
        Map<String, String> result = wxPay.orderQuery(map);
        String returnCode = result.get("return_code");
        String resultCode = result.get("result_code");
        if ("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)) {
            String tradeState = result.get("trade_state");
            if ("SUCCESS".equals(tradeState)) {
                UpdateWrapper updateWrapper = new UpdateWrapper();
                updateWrapper.eq("code", code);
                updateWrapper.set("status", 2);
                updateWrapper.set("payment_type",1);
                orderService.update(updateWrapper);
                return R.ok("订单状态已修改");
            } else {
                return R.ok("订单状态未修改");
            }
        }
        return R.ok("订单状态未修改");
    } catch (Exception e) {
        e.printStackTrace();
        return R.error("查询支付订单失败");
    }
}

编写order.vue
uni.request({
    url:that.url.wx.updateOrderStatus,
    method:"POST",
    header:{
        "token":uni.getStorageSync("token")
    },
    data:{
        "orderId":id
    },
    success:function(resp){
        let pages=getCurrentPages()
        let page=pages[pages.length-1]
        page.onShow()
    },
    fail:function(){
        console.log("更新订单状态失败")
    }
})


总结

以上就是今天要讲的内容,本文仅仅简单介绍了微信支付中微信小程序支付的使用。
参考了开源项目renren-fast
链接: link

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;