前言
微信支付以前就听说过,身边的同事也有弄过,但是自己因为没有遇到相关业务因此也没有去研究过。最近工作上可能会遇到微信支付因此也进行了些许研究,只是做到了接口掉通而已,并没有太深入,对微信支付已经很熟悉的同学请绕道走。
微信支付你需要了解内容
1.微信支付常用支付模式
JSAPI支付
JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。
APP支付
APP支付是指商户通过在移动端应用APP中集成开放SDK调起微信支付模块来完成支付。目前微信支付支持手机系统有:IOS(苹果)、Android(安卓)和WP(Windows Phone)
Native支付
Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。
小程序支付
商户已有微信小程序,用户通过好友分享或扫描二维码在微信内打开小程序时,可以调用微信支付完成下单购买的流程。
2.各种模式使用场景
支付模式 | 使用场景 |
JSAPI支付 | 线下场所、公众号场景和PC网站场景。 |
APP支付 | APP支付适用于在移动端APP中集成微信支付功能的场景 |
Native支付 | Native支付适用于PC网站、实体店单品或订单、媒体广告支付等场景 |
小程序支付 | 小程序中唤起支付界面 |
付款码支付 | ----- |
3.个人理解(C端-用户,B端-商家)
JSAPI支付:C端使用B端程序,B端直接拉起支付界面,由C端完成支付,如:微信支付中手机话费充值
APP支付:C端使用B端开发的APP时,在APP中拉起支付界面
Native支付:由B端生成一个支付二维码,然后C端进行扫码拉起支付界面支付,如:菜市场买菜时微信支付
小程序支付:和APP支付一个道理
4.支付接口联调准备参数
由于支付涉及到商家,因此必须搞到商家相关的几个参数(自己注册成为商家是需要营业执照的,因此各位同学自己想办法吧),我测试的时候是直接用的别的企业的相关参数,所以不能共享出来的,所需参数内容,如下:
参数名称 | 参数讲解 |
APPID | 由微信生成的应用ID,全局唯一。 |
商户号 | 直连商户的商户号,由微信支付生成并下发。 |
支付密钥 | 由商户提供用于进行验证支付 |
回调函数 | 由商户提供一个可外网访问的接口用于接收支付结果(不可带参数) |
商户订单号 | 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 |
5.支付接口认识
微信支付其实对每个支付模式都提供了一套完整的api接口,每个支付模式在官网都有非常详情的Api文档,此处我研究的是Native支付,因此提供一个Native的文档参考地址。
提示:微信支付并不是我们所想的简单的调用一下接口就OK了,比如Native支付是需要我们先调用下单接口(自己指定一个不重复的订单号),生成一个订单并返回二维码链接,用户扫码支付完成后,微信会自动调用我们提供的回调地址,将支付结果反馈给我们。我们下单生成的订单也是有用处的,比如进行支付的订单查询,退款等等
接口调用
此处我只实现了Native方式中的下单和回调函数的开发,至于其他接口也是同样的道理,大家可以举一反三。
1.下单接口
@GetMapping("/pay/{orderId}")
public void wxPay(@PathVariable("orderId")String orderId, HttpServletResponse response) throws Exception {
// 产品相关参数
WeChatParams ps = new WeChatParams();
ps.setBody("商品描述");
// 商品价格,注意是以分为单位,后面不能有小数点
ps.setTotal_fee("1");
// 订单ID
ps.setOut_trade_no(orderId);
// 附加参数,随便设置
ps.setAttach(UUID.randomUUID().toString().replace("-",""));
/**构造请求参数*/
SortedMap<Object, Object> packageParams = new TreeMap<>();
packageParams.put("appid", WeChatProperties.appId);
packageParams.put("mch_id", WeChatProperties.mchId);
// 随机字符串 【备注:每次发起请求都需要随机的字符串,否则失败。】
packageParams.put("nonce_str", UUID.randomUUID().toString().replace("-",""));
// 支付的商品名称
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", WeChatProperties.weChat_notify_url_pc);
// 支付方式
packageParams.put("trade_type", WeChatProperties.tradeType);
// 限制用户不能使用信用卡
packageParams.put("limit_pay", "no_credit");
// 设备号
packageParams.put("device_info", "WEB");
// //额外的参数
packageParams.put("attach", ps.getAttach());
// 设置签名
String sign = PayForUtil.createSign("UTF-8", packageParams, WeChatProperties.apiKey);
packageParams.put("sign", sign);
String requestXML = PayForUtil.getRequestXml(packageParams);
System.out.println("支付下单接口请求参数:" + requestXML);
/**调用微信统一下单API返回二维码的code*/
String resXml = HttpUtil.postData(WeChatProperties.ufdoderUrl, requestXML);
Map map = XMLUtil.doXMLParse(resXml);
System.out.println("微信下单接口响应结果:" + resXml);
if (map.get("return_code").equals("SUCCESS") && map.get("result_code").equals("SUCCESS")) {
// 二维码地址
String result = (String) map.get("code_url");
//生成二维码输出给前台
encodeQrcode(result, response);
} else {
throw new RuntimeException(map.get("err_code_des").toString());
}
/*
//测试把二维码输出到本地,正式环境通过流给前端
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);*/
}
2.将地址生成二维码工具方法
@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();
}
}
private 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;
}
3.回调方法
@RequestMapping("/notify")
public void wechatNotifyUrlPc(HttpServletRequest request, HttpServletResponse response) throws Exception {
BufferedReader reader = request.getReader();
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
String xmlString = buffer.toString();
request.getReader().close();
// 验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
if (!WXPayUtil.isSignatureValid(xmlString, WeChatProperties.apiKey)) {
System.out.println("验证签名结果[失败].");
} else {
Map<String, String> map = XMLUtil.doXMLParse(xmlString);
String resultCode = map.get("result_code");
if ("SUCCESS".equalsIgnoreCase(resultCode)) {
// TODO 支付成功,做后续业务逻辑处理
System.out.println("支付成功,请继续作业");
} else {
// TODO 失败,做失败业务逻辑处理
System.out.println("支付失败,请排查原因");
}
}
// 响应微信端,阻止其持续调用
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
}
小结
本文只是简单的贴出来核心的代码,并不是很全面,需要全面的代码的同学请移步到码云下载我的源码哈。本文的内容,经过清测,是可以调用并接收通知的,请大家放心使用。
源码地址:https://gitee.com/zhaobolan/wechat_pay_native