-
准备工作
在开发微信小程序支付接口之前,首先得为微信小程序申请接入微信支付,不麻烦,给个连接:微信支付接入指引
跟着指引走,很简单,目前,小程序开发的微信支付接口只支持企业级开发的小程序,需提交相关企业经营相关资质,个人暂时不支持开通支付,过程中要支付300¥,最终会拿到开发需要的需要的“商户号”、“商户api安全key”,这个key是自己在商户后台设置的的32位秘钥,生成方式自己选择,百度一堆;(这两个比较重要,放在后台,不可以放在小程序前台)。
除了获取到“商户号”和“api安全key”之外,我们还需要在商户后台获取到“API证书”,至于什么是“API证书”、如何获取的问题,百度一堆,这里给个参考:什么是API证书?如何获取API证书
最终我们会获取到3个文件,apiclient_cert.p12、apiclient_cert.pem、apiclient_key.pem,其中 apiclient_cert.p12 对我们是最重要的,将它放入到我们项目的资源文件目录下;

-
业务流程
在进行开发之前,深刻理解业务流程很重要,这里可以参考官方的:微信小程序支付业务流程时序图

商户系统和微信支付系统主要交互:
1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2、商户server调用支付统一下单,api参见公共api【统一下单API】
3、商户server调用再次签名,api参见公共api【再次签名】
4、商户server接收支付通知,api参见公共api【支付结果通知API】
5、商户server查询支付结果,api参见公共api【查询订单API】
上面提到的交互中的第一条,登录接口,上一篇博文已经完成了,这里就可以不用做了,切勿重复调用登录接口,会刷新之前的session_key,造成用户登陆授权流程的混乱,引起不必要的麻烦。
-
开发方式的选择
这里我选择的是使用微信官方提供的SDK开发,可以省去我们很多事情:官方SDK下载地址

可以看到官方java版SDK下载下来共有8个java文件,主要是一些封装好的工具类和配置文件,我们将这些java文件复制到我们的项目下,可以为其创建一个package包;
开发代码
首先,我们在sdk文件的同级目录,创建一个自己的配置类,MyConfig,继承sdk自带的WXPayConfig,重写其中的抽象方法,在构造函数中要读取我们的配置文件 apiclient_cert.p12 ,这个在上面讲过了;
MyConfig.java
package com.wx.video.wxpay.sdk;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.ClassPathResource;
/**
** 功能描述:自定义的微信支付配置类
* @Package: com.github.wxpay.sdk
* @author: jiguiquan
* @date: 2019年6月13日 上午11:08:35
*/
public class MyConfig extends WXPayConfig {
//读取证书文件返回的字节数组
private byte[] certData;
public MyConfig() throws Exception {
try {
ClassPathResource classPathResource = new ClassPathResource("apiclient_cert.p12");
//获取文件流
InputStream certStream = classPathResource.getInputStream();
this.certData = IOUtils.toByteArray(certStream);
certStream.read(this.certData);
certStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//我的appid
@Override
String getAppID() {
return "wx0fb1335345abd2544";
}
//我的商户号
@Override
String getMchID() {
return "15364354501";
}
//我的商户api安全key,在商户平台api安全目录下可设置
@Override
String getKey() {
return "xKLPpyJOORlkVm35435545tx1PuxqqIx";
}
@Override
InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
return 8000;
}
@Override
public int getHttpReadTimeoutMs() {
return 10000;
}
@Override
IWXPayDomain getWXPayDomain() {
return new IWXPayDomain() {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
@Override
public DomainInfo getDomain(WXPayConfig config) {
return new DomainInfo("api.mch.weixin.qq.com", false);
}
};
}
}
做完上面的一系列工作后,就可以在Controller中上手开发我们的统一微信支付接口啦,当然更优的做法是将方法都封装起来,或者提取到service层,但是我这里为了直观,就直接将逻辑全部写到controller层了;
/**
** 微信支付接口
*
* @param order 实际业务订单详情
* @param request
* @return
*/
@RequestMapping(value = "/wxPay", method = RequestMethod.POST)
@ResponseBody
public JsonResult wxPay(@RequestBody Vorder order, HttpServletRequest request) {
// 从request中解析出当前用户的个人信息
Claims claims = jwtUtils.getUserClaim(request);
String uid = claims.get("uid").toString();
String openid = claims.get("openid").toString();
// 判断是否重复购买
List<Integer> list = vorderService.myOrderVidList(Integer.parseInt(uid));
System.out.println(list);
if (list.contains(order.getVid())) {
return JsonResult.error("当前课程你已经购买过,无需重复购买");
}
// 没有购买过的情况下,则记得在数据库中插入订单信息,为了后面的模板消息推送中能获取prepay_id,我需要等到第一次签名结束后,得到prepay_id后,才向数据库新增这条订单数据
Map resultMap = new HashMap(); // 这个resultMap是用来封装最终返回给小程序的数据的
// 开始进入构建微信支付环境
MyConfig config = null;
WXPay wxpay = null;
try {
config = new MyConfig();
wxpay = new WXPay(config);
} catch (Exception e) {
e.printStackTrace();
}
// 使用官方sdk工具生成商户订单号
String out_trade_no = WXPayUtil.generateNonceStr();
// 生成的随机字符串
String nonce_str = WXPayUtil.generateNonceStr();
// 获取客户端的ip地址
// 获取本机的ip地址
InetAddress addr = null;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
String spbill_create_ip = addr.getHostAddress();
// 支付金额,需要转成字符串类型,否则后面的签名会失败
int total_fee = (int) (order.getOprice() * 100);
System.out.println("付款金额为=" + total_fee);
// 商品描述
String body = order.getDescription();
// 统一下单接口参数
HashMap<String, String> data = new HashMap<String, String>();
data.put("appid", "wx0fb1142123bd2544");
data.put("mch_id", "15364312101");
data.put("nonce_str", nonce_str);
data.put("body", body);
data.put("out_trade_no", out_trade_no);
data.put("total_fee", String.valueOf(total_fee));
data.put("spbill_create_ip", spbill_create_ip);
data.put("notify_url", "https://www.testtuan.com/wxvideo/api/order/notify");
data.put("trade_type", "JSAPI");
data.put("openid", openid);
try {
Map<String, String> rMap = wxpay.unifiedOrder(data); //这里是在调用统一下单API,已经被官方的SDK封装好了;
System.out.println("统一下单接口返回: " + rMap);
String return_code = (String) rMap.get("return_code");
String result_code = (String) rMap.get("result_code");
String nonceStr = WXPayUtil.generateNonceStr();
resultMap.put("nonceStr", nonceStr);
Long timeStamp = System.currentTimeMillis() / 1000;
if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) {
String prepayid = rMap.get("prepay_id");
// 在第一次签名之后,再进行预付订单的创建,这样可以同时将prepay_id一并存入数据库
order.setOutTradeNo(out_trade_no);
order.setUid(Integer.parseInt(uid));
order.setOpenid(openid);
order.setStatus("0");
order.setOtime(new Date());
order.setOtype("wxPay");
order.setPrepayId(prepayid);
int count = vorderService.save(order);
if (count != 1) {
return JsonResult.error("新增订单失败");
}
System.out.println("新增订单成功");
// 开始进行第二次签名
resultMap.put("package", "prepay_id=" + prepayid);
resultMap.put("signType", "HMAC-SHA256");
// 这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
resultMap.put("timeStamp", timeStamp + "");
// 再次签名,这个签名用于小程序端调用wx.requesetPayment方法
resultMap.put("appId", "wx0fb1dssfsf2544");
String sign = WXPayUtil.generateSignature(resultMap, "xKLPpyJsdffjovptx1PuxqqIx");
resultMap.put("paySign", sign);
System.out.println("生成的签名paySign : " + sign);
System.out.println("返回结果为" + resultMap);
return JsonResult.successs(resultMap);
} else {
return JsonResult.error("失败了");
}
} catch (Exception e) {
e.printStackTrace();
return JsonResult.error("出错了");
}
}
到这一步,如果第一次签名(统一下单API)和第二次签名都成功的话,小程序前端就可以收到我们返回的5和参数啦,然后调用wx.requestPayment()接口就可以发起支付啦:
示例代码:
wx.requestPayment(
{
'timeStamp': '',
'nonceStr': '',
'package': '',
'signType': 'MD5',
'paySign': '',
'success':function(res){},
'fail':function(res){},
'complete':function(res){}
})
然后用户界面会弹出支付确认,用户确认后,则支付流程就成功啦;这里可参考:小程序调起支付API
注意
1、上面所有的常量,我都是直接写在它应该出现的地方的,实际开发中,注意使用常量类统一管理,这些值都是我复制上来后,随手敲的假值,所以上下文有可能对应不上;
2、支付后微信会异步通知我们支付结果,通知的地址就是我们在统一下单时候,put进去的参数notify_url,这个url必须为外网可访问的url,不难理解;
3、我在支付这里被卡了很久,无论后台代码检查还是前台代码检查都没有问题,用户确认支付后一直显示,“支付签名验证失败”,网上百度很好几天,各种别人遇到的问题都排查了,比如“appid大小写问题”,“二次签名顺序问题”等等,最后不报希望地更换了一下加密算法(官方推荐,网上教程,默认全都是MD5算法),其实还有另外一种加密算法,即“HMAC-SHA256”加密算法,最后竟然生效了,支付过程顺利完成,所以如果有人跟我遇到相同的问题,在百度上面的方法都无效的时候,可以尝试我的方法,将加密算法修改为“HMAC-SHA256”,记住,除了自己的代码里,SDK中也需要修改,小程序前端发起支付时候的signType也需要跟着修改;
4、另外提醒一下:虽然小程序官方要求授权的域名必须是带https证书的,但是在开发过程中,忽略了https域名选项是完全可以支持开发和完成支付的,刚开始我遇到前面失败时候,各种方法尝试无果,一度怀疑是不是https域名没有落实的原因,实际正常,并不是,如果你也遇到了此问题,不要怀疑。
有了上面的代码,小程序微信支付环境就完成啦;由于篇幅原因,至于“异步回调通知”我就留着下篇博文讲述了,如果篇幅不长,我会将异步回调通知和“模板消息”在一篇博文里面讲述。
此致敬礼




1 Comment
跟着楼主的教程顺利搞定了,非常感激,想让楼主请我吃饭