系列文章目录
支付
微信支付
微信小程序支付
文章目录
前言
现在电商平台这么发达,我们经常在网上购物,能选用的支付聚道大概有以下三种。
第一种是微信支付,我觉得在线下环境,用微信支付是最多的。因为手机长时间运行微信,所以掏出手机,直接拿微信就扫码支付了。虽然商家分别提供了微信和支付宝的付款码,但是习惯上,还是喜欢用微信扫码付款。另外,发红包、抢红包,以及微信转账,也都用上了微信支付接口。微信支付的优点是简单易用,但是缺点也很明显,就是专业化不足。并没有把支付跟金融产品、信用借贷、以及生活缴费联系在一起。
这方面支付宝做的更好,可以在支付宝上面缴纳各种费用,甚至说查询医保社保也都没问题。去医院之前,可以在支付宝上面挂号。觉得父母年纪大了,可以在支付宝上面购买健康体检服务。至于说金融和保险产品,支付宝上面就更多了。而且支付宝有个杀手级的伙伴,那就是淘宝,在淘宝上面优先推荐的是花呗渠道,然后才是支付宝上面绑定的各种银行卡渠道。每年被淘宝分出去的支付流量,就让微信看的眼红。所以中国主流的支付渠道还是支付宝平台。
最后我要说的是银联的这个平台。以前银联的付款渠道门槛很高,买家和卖家用起来都不方便。比如说线下渠道,商家必须要安装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