微信Native支付简单实现
因公司需求需要在网页上做一个扫码支付的功能,首先介绍一下基本的流程
1.用户在前台点击下单,此时订单状态为未支付,
2.之后订单的一系列处理之后,会在前端跳出一个二维码,提示用户扫码支付
3.生成二维码之后订单状态修改为支付中
4.用户成功扫码支付之后,微信会向我们的项目发送一个用户支付成功的请求
5.我们接收到请求把订单状态修改为已支付
微信支付提供的几种对接方式
基于上面的需求分析,文章主要介绍的是对接Native方式
Native方式
官方说明:
Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站、实体店单品或订单、媒体广告支付等场景.开发文档
解析: 商户系统指的就是我们自己的项目,按照微信支付协议生成支付二维码 那么微信支付协议是什么不清楚,暂缓,往下看,用户扫一扫支付简单,是基本的流程,适用于PC网站、实体店单品或订单、媒体广告支付等场景,那么这些场景都是以什么方式支付的呢,PC网站,比如在网上购买一个商品,选择微信或支付包支付会跳出一个二维码,扫码之后就是直接支付,不需要我们手动填写金额之类的,支付成功之后会告诉你一个支付中或者支付成功或失败的信息
JSAPI支付
JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。开发文档
应用场景有:
线下场所:调用接口生成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
公众号场景:用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
PC网站场景:在网站中展示二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
解析:乍一看和上面的Native方式好像没区别都是调用接口生成二维码,只是说明了在微信浏览器中打开页面完成支付,具体的可以研究一下,目前我也不是很清楚
APP支付
APP支付是指商户通过在移动端应用APP中集成开放SDK调起微信支付模块来完成支付。适用于在移动端APP中集成微信支付功能的场景。开发文档
这个说的很明白了,移动端应用,不存在二维码,直接调用微信,就像手机上选择微信支付之后会直接跳到微信开始支付流程
H5支付
H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。开发文档
小程序支付
小程序支付是指商户通过调用微信支付小程序支付接口,在微信小程序平台内实现支付功能;用户打开商家助手小程序下单,输入支付密码并完成支付后,返回商家小程序。开发文档
正文开始
首先说明微信沙箱环境也需要提交乱七八糟的很多东西申请,而且听说有很多坑,建议直接使用正式环境
不像支付宝个人账号也可以使用沙箱环境
Native方式需要三个主要的东西
mchId:就是登录的时候的账号
AppID:登录进去再产品中心—>开发配置中查看
需要注意的是这个APPID需要和微信公众号绑定,不然调接口会报一个Appid和密钥不匹配的错,这个请自行百度绑定方式
ApiKey:登录进去在账户中心—>API安全里面配置 注意这个只要是32位的字符就可以,可以手动输入,也可以使用工具生成 ,第一次设置需要设置一个操作密码还是什么的,设置一下就可以了,设置成功之后在使用这个密码去设置APIKey
在线生成地址:http://suijimimashengcheng.51240.com 或 http://www.sexauth.com/
微信商户平台 :https://pay.weixin.qq.com
二维码生成
上面说到了Native支付必须按照微信支付协议生成二维码,那么二维码到底是个什么东西呢,简单介绍一下,码有很多种,像零食上面的条码是一种一维码,里面放不了很多东西,反正后来就出现了二维码,里面可以包含很多信息,可以用java的面向对象思想理解,二维码就是一个对象,里面有很多的属性值,那么我们是不是能在二维码里面放自己想放的东西呢,当然是可以的,我使用的是谷歌的一款二维码生成框架
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.0</version>
</dependency>
public class QrCodeTest {
public static void main(String[] args) {
try {
//二维码中的内容,随便设置
String content = "大象哥二维码测试";
//对二维码属性封装的一个对象
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
Map hints = new HashMap();
//设置二维码的编码
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
//内容,生成二维码的方式使用QR,还有其他的可以自行研究
// 市场常用的就是这个,图片的尺寸那些,最后就是二维码的编码了, 还有一些其他的可以研究一下
BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, 400, 400, hints);
File file = new File("C:/Users/62601/Desktop/zssjlog/qr.jpg");
//调用工具类把二维码生成在本地,正式项目肯定不是保存在本地
//回通过流的方式输出给前台
MatrixToImageWriter.writeToFile(bitMatrix, "jpg", file);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
生成的二维码
扫描之后会发现叫你复制去浏览器打开,这是为啥??? 没有按照微信的二维码规则生成的二维码,微信客户端肯定解析不了这个二维码啊,去浏览器打开,就能看到我们之前设置进去的值的
ok,现在我们知道了怎么生成二维码,就只有一个问题了,微信的二维码规则是什么,更具体的就是微信支付的二维码规则是什么,好了,这个问题不需要我们去了解,具体的微信肯定很复杂,不是我们这种小渣渣可以理解的
其实我们只需要向微信统一下单接口发送请求,他会给你返回一个字符串,使用这个字符串生成的二维码就是按照了微信二维码规则的
说明一下统一下单接口,就是一个接口的公网路径,我们需要使用工具向微信发送请求,后台发送请求的方式就不说了,接口需要的请求参数需要时XML格式的字符串,这个也比较麻烦,而且需要参数按照 啊斯克码表(asl…) 排序,使用TreeMap就可以了,默认就是那么排序的
代码示例:源码才是做好的API
为了方便,把所有代码都放在controller
/**
* @author
*/
@Slf4j
@RestController
@RequestMapping("/wx")
public class WeixinPayController {
private static final int BLACK = 0xff000000;
private static final int WHITE = 0xFFFFFFFF;
/**
* pc端微信支付之后的回调方法
*
* @param request
* @param response
* @throws Exception
*/
@RequestMapping("/notify")
public void wechatNotifyUrlPc(HttpServletRequest request, HttpServletResponse response) throws Exception {
}
//微信支付接口
@GetMapping("/pay")
public void wxPay(HttpServletResponse response) throws Exception {
WeChatParams ps = new WeChatParams();
ps.setBody("商品描述");
//商品价格,注意是以分为单位,后面不能有小数点
// 测试用一分
ps.setTotal_fee("1");
//自己项目的订单ID
ps.setOut_trade_no("123");
//附加参数,随便设置
ps.setAttach("111");
//调用微信统一下单API返回二维码的code
/**
* 账号信息
*/
String appid = WeChatProperties.appId;//微信服务号的appid
String mch_id = WeChatProperties.mchId; //微信支付商户号
String key = WeChatProperties.apiKey; // 微信支付的API密钥
String notify_url = WeChatProperties.weChat_notify_url_pc;//回调地址【注意,这里必须要使用外网的地址】
String ufdoder_url = WeChatProperties.ufdoderUrl;//微信下单API地址
String tradeType = WeChatProperties.tradeType;//微信下单API地址
/**
* 时间字符串
*/
String currTime = PayForUtil.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayForUtil.buildRandom(4) + "";
String nonce_str = strTime + strRandom;
/**
* 参数封装
*/
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
packageParams.put("nonce_str", nonce_str);//随机字符串
packageParams.put("body", ps.getBody());//支付的商品名称
packageParams.put("out_trade_no", ps.getOut_trade_no());//商户订单号【备注:每次发起请求都需要随机的字符串,否则失败。】
packageParams.put("total_fee", ps.getTotal_fee());//支付金额
packageParams.put("spbill_create_ip", PayForUtil.localIp());//客户端主机
packageParams.put("notify_url", notify_url);//回调地址就是上面的路径
packageParams.put("trade_type", tradeType);//支付方式
packageParams.put("limit_pay", "no_credit");//限制用户不能使用信用卡
packageParams.put("device_info", "WEB");//设备号
packageParams.put("attach", ps.getAttach());//额外的参数【业务类型+会员ID+支付类型】
String sign = PayForUtil.createSign("UTF-8", packageParams, key); //获取签名
packageParams.put("sign", sign);
String requestXML = PayForUtil.getRequestXml(packageParams);//将请求参数转换成String类型
log.info("微信支付请求参数的报文" + requestXML);
String resXml = HttpUtil.postData(ufdoder_url, requestXML); //解析请求之后的xml参数并且转换成String类型
Map map = XMLUtil.doXMLParse(resXml);
log.info("微信支付响应参数的报文" + resXml);
String result;
if (map.get("return_code").equals("SUCCESS") && map.get("result_code").equals("SUCCESS")) {
result = (String) map.get("code_url");
} else {
throw new RuntimeException(map.get("err_code_des").toString());
}
String urlCode = getCodeUrl(ps);
//生成二维码输出给前台
//encodeQrcode(urlCode, response);
/*
//测试把二维码输出到本地,正式环境通过流给前端
HashMap<EncodeHintType, String> map = new HashMap<>();
map.put(EncodeHintType.CHARACTER_SET, "utf-8");
BitMatrix bitMatrix;
bitMatrix = new MultiFormatWriter().encode(urlCode, BarcodeFormat.QR_CODE, 250, 250,map );
StringBuilder path =new StringBuilder();
path.append("C:\\Users\\Administrator\\Desktop\\demo\\");
path.append("wx22.jpg");
Path file = new File(path.toString()).toPath();
MatrixToImageWriter.writeToPath(bitMatrix, "jpg", file);*/
}
public String getCodeUrl(WeChatParams ps) throws Exception {
/**
* 账号信息
*/
String appid = WeChatProperties.appId;//微信服务号的appid
String mch_id = WeChatProperties.mchId; //微信支付商户号
String key = WeChatProperties.apiKey; // 微信支付的API密钥
String notify_url = WeChatProperties.weChat_notify_url_pc;//回调地址【注意,这里必须要使用外网的地址】
String ufdoder_url = WeChatProperties.ufdoderUrl;//微信下单API地址
String tradeType = WeChatProperties.tradeType;//微信下单API地址
/**
* 时间字符串
*/
String currTime = PayForUtil.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayForUtil.buildRandom(4) + "";
String nonce_str = strTime + strRandom;
/**
* 参数封装
*/
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
packageParams.put("nonce_str", nonce_str);//随机字符串
packageParams.put("body", ps.getBody());//支付的商品名称
packageParams.put("out_trade_no", ps.getOut_trade_no());//商户订单号【备注:每次发起请求都需要随机的字符串,否则失败。】
packageParams.put("total_fee", ps.getTotal_fee());//支付金额
packageParams.put("spbill_create_ip", PayForUtil.localIp());//客户端主机
packageParams.put("notify_url", notify_url);//回调地址
packageParams.put("trade_type", tradeType);//支付方式
packageParams.put("limit_pay", "no_credit");//限制用户不能使用信用卡
packageParams.put("device_info", "WEB");//设备号
packageParams.put("attach", ps.getAttach());//额外的参数【业务类型+会员ID+支付类型】
String sign = PayForUtil.createSign("UTF-8", packageParams, key); //获取签名
packageParams.put("sign", sign);
String requestXML = PayForUtil.getRequestXml(packageParams);//将请求参数转换成String类型
log.info("微信支付请求参数的报文" + requestXML);
String resXml = HttpUtil.postData(ufdoder_url, requestXML); //解析请求之后的xml参数并且转换成String类型
Map map = XMLUtil.doXMLParse(resXml);
log.info("微信支付响应参数的报文" + resXml);
String result;
if (map.get("return_code").equals("SUCCESS") && map.get("result_code").equals("SUCCESS")) {
result = (String) map.get("code_url");
} else {
throw new RuntimeException(map.get("err_code_des").toString());
}
return result;
}
/**
* 将路径生成二维码图片
*
* @param content
* @param response
* @author pzh
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static void encodeQrcode(String content, HttpServletResponse response) {
if (StringUtils.isBlank(content)) {
return;
}
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
Hashtable hints = new Hashtable();
//设置容错级别最高
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
//设置字符编码为utf-8
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
//二维码空白区域,最小为0也有白边,只是很小,最小是6像素左右
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix = null;
try {
bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, 250, 250, hints);
BufferedImage image = toBufferedImage(bitMatrix);
//输出二维码图片流
try {
response.setHeader("Content-Type", "image/jpeg");
ImageIO.write(image, "png", response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
} catch (WriterException e1) {
e1.printStackTrace();
}
}
/**
* 类型转换
*
* @param matrix
* @return
* @author pzh
*/
public static BufferedImage toBufferedImage(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) == true ? BLACK : WHITE);
}
}
return image;
}
}
其他工具类都在码云上面,可以自行下载
链接: https://gitee.com/huang_jia_jun/WXPay-Native/tree/master