扫码支付 (基于微信)

1. 二维码

1.1 什么是二维码

  • 二维码又称 QR Code,QR 全称 Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的 BarCode 条形码能存更多的信息,也能表示更多的数据类型。
  • 二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。

1.2 二维码优势

  • 信息容量大, 可以容纳多达 1850 个大写字母或 2710 个数字或 500 多个汉字
  • 应用范围广, 支持文字,声音,图片,指纹等等…
  • 容错能力强, 即使图片出现部分破损也能使用
  • 成本低, 容易制作

1.3 二维码容错级别

  • L 级(低) 7%的码字可以被恢复。
  • M 级(中) 的码字的 15%可以被恢复。
  • Q 级(四分)的码字的 25%可以被恢复。
  • H 级(高) 的码字的 30%可以被恢复。

1.4 二维码生成插件 qrious

  • qrious 是一款基于 HTML5 Canvas 的纯 JS 二维码生成插件。通过 qrious.js 可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行 Base64 编码。

qrious.js 二维码插件的可用配置参数如下:

参数 类型 默认值 描述
background String “white” 二维码的背景颜色。
foreground String “black” 二维码的前景颜色。
level String “L” 二维码的误差校正级别(L, M, Q, H)。
mime String “image/png” 二维码输出为图片时的 MIME 类型。
size Number 100 二维码的尺寸,单位像素。
value String “” 需要编码为二维码的值

下面的代码即可生成一张二维码

<html>
    <head>
        <title>二维码入门小 demo</title>
    </head>
    <body>
        <img id="qrious">
        <script src="qrious.min.js"></script>
        <script>
            var qr = new QRious({
                element: document.getElementById('qrious'),
                size: 250, 
                value: 'http://www.baidu.com'
            });
        </script>
    </body>
</html>

运行效果:
这里写图片描述

2. 微信扫码支付

2.1 开发文档

  • 按 API 要求组装参数,以 XML 方式发送(POST)给微信支付接口(URL),微信支付接口也是以 XML 方式给予响应。程序根据返回的结果(其中包括支付 URL)生成二维码或判断订单状态。
  • 在线微信支付开发文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
  • 我们在本章中会用到”统一下单”和”查询订单”两组 API
    1. appid:微信公众账号或开放平台 APP 的唯一标识
    2. mch_id:商户号 (配置文件中的 partner)
    3. partnerkey:商户密钥
    4. sign: 数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

2.2 微信支付 SDK

  • 微信支付提供了 SDK, 大家下载后打开源码,install 到本地仓库
    这里写图片描述

  • 也可以使用 maven 工程中引入微信支付 SDK 的依赖坐标

<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>
  • 我们主要会用到微信支付 SDK 的以下功能:

// 获取随机字符串
String str = WXPayUtil.generateNonceStr()

// MAP 转换为 XML 字符串(自动添加签名)
// 参数:  maps: 需要传递的参数 Map 集合, partnerkey : 商户密钥
String xmlParam = WXPayUtil.generateSignedXml(maps, partnerkey);

//XML字符串转换为 Map 对象
Map<String, String> stringStringMap = WXPayUtil.xmlToMap(xmlMap);

2.3 HttpClient 工具类

  • HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。
  • HttpClient 通俗的讲就是模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用HttpClient.

关于 HttpClient(原生)具体的使用不属于我们本章的学习内容,我们这里这里为了简化 HttpClient 的使用,提供了工具类 HttpClient(对原生 HttpClient 进行了封装)

  • HttpClient 工具类使用的步骤
// 创建客户端
HttpClient client = new HttpClient(请求的 url 地址);
//是否是 https 协议
client.setHttps(true); 
//发送的 xml 数据(String类型)
client.setXmlParam(xmlParam); 
//执行 post 请求
client.post();
//获取结果
String result = client.getContent(); 
  • 使用时需导入依赖
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

2.4 工程搭建与准备工作

  • 建立支付服务接口模块 pay-interface (jar)
  • 建立支付服务实现模块 pay-service(war)依赖 pay-interface 和 pay-common 、 spring 、 dubbo 相关依赖 、 微信 SDK (因为不需要连接数据库所以不用引用 dao 工程)
<!-- 微信SDK -->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>
<!-- spring,dubbo 等其他依赖自行添加 -->
  • 在pay-common 工程中添加工具类 HttpClient.java ,并添加依赖
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
  • 添加配置文件 weixinpay.properties
# appid:微信公众账号或开放平台 APP 的唯一标识
appid=wx8397f8696b5

# partner:财付通平台的商户账号 (mch_id:商户号)
partner=1473426802

# partnerkey:财付通平台的商户密钥
partnerkey=T6m9iK73b0kn9g5v426MKfHQ

# notifyurl: 回调地址, 支付成功后微信服务器向回调地址发送信息
notifyurl=http://********/WeChatPay/WeChatP

# payUrl : 远程支付接口, 用于生成支付的 url
payUrl=https://api.mch.weixin.qq.com/pay/unifiedorder
  • pinyougou-cart-web 依赖工程 pinyougou-pay-service
  • 将二维码插件 QRious 拷贝到 pinyougou-cart-web 的 plugins 目录中

2.5 代码实现

2.5.1 前端

2.5.1.1 页面
  • 修改 pay.html ,引入 js
<script type="text/javascript" src="plugins/angularjs/angular.min.js"> </script>
<script type="text/javascript" src="js/base.js"> </script>
<script type="text/javascript" src="js/service/payService.js"> </script>
<script type="text/javascript" src="plugins/qrious.min.js"></script>

<!-- controller层要放在元素 <img id="qrious"> 之后,否则js代码无法获取到 #qrious 元素-->
<script type="text/javascript" src="js/controller/payController.js"> </script>
  • 指令
<body ng-app="pyg" ng-controller="payController" ng-init="createQrCode()">
  • 设置二维码图片的 ID
<div class="fl code">
     <img id="qrious" >
     <div class="saosao">
         <p>请使用微信扫一扫</p>
         <p>扫描二维码支付</p>
     </div>
 </div>
  • 显示订单号
<span class="success-info">
    订单提交成功,请您及时付款!订单号:{{out_trade_no}}
</span>
  • 显示金额
<span class="fr">
    <em class="sui-lead">应付金额:</em>
    <em class="orange money">{{total_fee}}</em></span>
2.5.1.2 控制层 payController.js
//控制层 
app.controller('payController', function ($scope, $location,payService) {
    //生成二维码
    //1,向支付系统发送生成二维码请求
    //2,支付系统调用微信支付品台支付下单接口
    //3,微信支付品台返回支付地址
    //4,根据此地址生成二维码
    $scope.createQrCode = function () {
        //调用服务层方法,向微信支付品台下单,获取支付地址
        payService.createQrCode().success(function (data) {
            //获取支付地址
            var code_url = data.code_url;
            //支付金额
            $scope.total_fee = data.total_fee;
            //订单号
            $scope.out_trade_no = data.out_trade_no;
            //使用qrious插件生成二维码
            var qr = new QRious({
                element: document.getElementById('qrious'),
                size: 300,
                background: 'white',
                foreground: 'black',
                level: 'H',
                value: code_url
            });

            //调用方法
            queryStatus($scope.out_trade_no);


        })

    };

    //实时监控二维码支付状态
    //每3秒查询一次二维码支付状态,如果支付成功,跳转到支付成功页面,否则跳转到支付失败页面
    queryStatus = function (out_trade_no) {
        //调用服务层方法,查询订单状态
        payService.queryStatus(out_trade_no).success(function (data) {
            //判断
            if (data.success) {
                //跳转到支付成功页面
                location.href = "paysuccess.html#?money="+$scope.total_fee;
            } else if (data.message == "timeout") {
                //重新生成二维码
                $scope.createQrCode();
            }
            else {
                location.href = "payfail.html";
            }
        })
    };

    //获取支付成功后金额
    $scope.loadTotalFee = function () {
        $scope.money = $location.search()["money"];
    }
}); 
2.5.1.3 服务层
//服务层
app.service('payService', function ($http) {

    //生成二维码
    this.createQrCode = function () {
        return $http.get('../pay/createQrCode');
    };

    //监控二维码支付状态
    this.queryStatus = function (out_trade_no) {
        return $http.get('../pay/queryStatus/'+out_trade_no);
    }

});

2.5.2 后端

2.5.2.1 控制层

import com.alibaba.dubbo.config.annotation.Reference;
import com.pyg.pay.service.PayService;
import com.pyg.utils.PygResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@RestController
@RequestMapping("/pay")
public class PayController {

    //注入远程支付对象
    @Reference(timeout = 10000000)
    private PayService payService;

    //生成二维码
    //1,向支付系统发送生成二维码请求
    //2,支付系统调用微信支付品台支付下单接口
    //3,微信支付品台返回支付地址
    //4,根据此地址生成二维码

    /**
     * 生成二维码
     *
     * @return
     */
    @RequestMapping("createQrCode")
    public Map createQrCode(HttpServletRequest request) {
        //获取用户名
        String userId = request.getRemoteUser();
        //调用服务层方法,向微信支付品台下单
        Map maps = payService.createQrCode(userId);
        return maps;
    }

    /**
     * 需求:实时监控二维码状态,及时发现二维码是否支付成功
     */
    @RequestMapping("queryStatus/{out_trade_no}")
    public PygResult queryStatus(@PathVariable String out_trade_no) {
        PygResult result = null;
        int i = 0;
        while (true) {
            //调用查询接口
            Map<String, String> map = payService.queryStatus(out_trade_no);
            if (map == null) {//出错
                result = new PygResult(false, "支付出错");
                break;
            }
            if (map.get("trade_state").equals("SUCCESS")) {//如果成功
                result = new PygResult(true, "支付成功");
                break;
            }
            try {
                Thread.sleep(3000);//间隔三秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            i++;

            if (i >= 100) {
                result = new PygResult(false, "timeout");
                break;
            }
        }
        return result;

    }

}

2.5.2.2 服务层接口

import java.util.Map;

public interface PayService {
    /**
     * 需求:生成二维码
     * @param userId
     * @return Map
     */
    public Map createQrCode(String userId);

    /**
     * 需求:查询支付二维码状态,查询是否支付成功
     * @param out_trade_no
     * @return
     */
    public Map queryStatus(String out_trade_no);

}

2.5.2.3 服务层实现类


import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPayUtil;
import com.pyg.mapper.TbOrderMapper;
import com.pyg.pay.service.PayService;
import com.pyg.pojo.TbOrder;
import com.pyg.pojo.TbOrderExample;
import com.pyg.utils.HttpClient;
import com.pyg.utils.IdWorker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class PayServiceImpl implements PayService {

    //注入商户appid
    @Value("${appid}")
    private String appid;

    //注入商户号
    @Value("${partner}")
    private String partner;

    //注入商户秘钥
    @Value("${partnerkey}")
    private String partnerkey;

    //注入微信支付接口
    @Value("${payUrl}")
    private String payUrl;

    // 回调地址
    @Value("${notifyurl}")
    private String notifyurl;

    //注入订单mapper接口代理对象
    @Autowired
    private TbOrderMapper orderMapper;

    /**
     * 需求:生成二维码
     *
     * @param userId 用户名
     * @return Map 封装的数据
     * 商户订单号和保存的订单订单号有关系没有? 支付订单
     */
    public Map createQrCode(String userId) {

        try {
            //查询支付金额
            //创建订单example对象
            TbOrderExample example = new TbOrderExample();
            //创建criteria对象
            TbOrderExample.Criteria criteria = example.createCriteria();
            //设置参数
            criteria.andUserIdEqualTo(userId);

            //执行查询
            List<TbOrder> orderList = orderMapper.selectByExample(example);

            //定义记录总金额变量
            Double totalFee = 0d;

            //循环多个订单,计算总金额
            for (TbOrder tbOrder : orderList) {
                totalFee += tbOrder.getPayment().doubleValue();
            }

            //创建一个map对象,封装支付下单参数
            Map<String, String> maps = new HashMap<>();
            //公众账号ID
            maps.put("appid", appid);
            //商户号
            maps.put("mch_id", partner);
            //随机字符串
            maps.put("nonce_str", WXPayUtil.generateNonceStr());
            //商品描述
            maps.put("body", "十里堡");

            //创建idworker对象,生成支付订单号
            IdWorker idWorker = new IdWorker();
            long payId = idWorker.nextId();

            //out_trade_no
            maps.put("out_trade_no", payId + "");

            //设置支付金额
            maps.put("total_fee", total_fee * 100);
            maps.put("spbill_create_ip", "127.0.0.1");
            maps.put("notify_url", notify_url);
            maps.put("trade_type", "NATIVE");

            //使用微信支付工具类对象,生成一个具有签名 sign 的xml格式参数(String类型)
            String xmlparam = WXPayUtil.generateSignedXml(maps, partnerkey);

            //创建httpClient对象,向微信支付品台发送请求,获取支付地址
            HttpClient httpClient = new HttpClient(payUrl);
            //设置请求方式
            httpClient.setHttps(true);
            //设置请求参数
            httpClient.setXmlParam(xmlparam);
            //设置请求方式
            httpClient.post();

            //获取回调结果
            String content = httpClient.getContent();
            //把xml格式转换成对象
            //返回支付地址
            Map<String, String> stringStringMap = WXPayUtil.xmlToMap(content);

            //金额
            stringStringMap.put("total_fee", totalFee + "");
            //支付订单号
            stringStringMap.put("out_trade_no", payId + "");

            return stringStringMap;


        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 需求:查询支付二维码状态,查询是否支付成功
     *
     * @param out_trade_no 支付订单号
     * @return 向微信支付品台发送请求,查询订单状态
     * 1,封装向微信传递参数
     * 2,把map参数转换为xml格式(此格式必须带签名)
     * 3,使用httpClient发送请求(向微信支付品台)
     * 4,获取微信支付返回结果
     * 5,把结果返回给表现层
     * 6,判断是否支付成功
     */
    public Map queryStatus(String out_trade_no) {

        try {
            //创建map对象,封装查询微信支付状态参数
            Map<String, String> maps = new HashMap<>();
            //商户appid 唯一标识
            maps.put("appid", appid);
            //商户号 唯一标识
            maps.put("mch_id", partner);
            //设置订单号
            maps.put("out_trade_no", out_trade_no);
            //随机字符串
            maps.put("nonce_str", WXPayUtil.generateNonceStr());

            //具有签名的xml格式参数
            String xmlParam = WXPayUtil.generateSignedXml(maps, partnerkey);

            //使用httpClient向微信支付品台发送查询订单请求
            HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
            //设置请求方式
            httpClient.setHttps(true);
            //设置参数
            httpClient.setXmlParam(xmlParam);
            //请求方式
            httpClient.post();

            //获取查询结果
            String xmlMap = httpClient.getContent();

            //把返回结果xml转换成map对象
            Map<String, String> stringStringMap = WXPayUtil.xmlToMap(xmlMap);

            //返回支付状态
            return stringStringMap;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

工具类

1.1 HttpClient 工具类


import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

/**
 * http请求客户端
 * 
 * @author Administrator
 * 
 */
public class HttpClient {
    private String url;
    private Map<String, String> param;
    private int statusCode;
    private String content;
    private String xmlParam;
    private boolean isHttps;

    public boolean isHttps() {
        return isHttps;
    }

    public void setHttps(boolean isHttps) {
        this.isHttps = isHttps;
    }

    public String getXmlParam() {
        return xmlParam;
    }

    public void setXmlParam(String xmlParam) {
        this.xmlParam = xmlParam;
    }

    public HttpClient(String url, Map<String, String> param) {
        this.url = url;
        this.param = param;
    }

    public HttpClient(String url) {
        this.url = url;
    }

    public void setParameter(Map<String, String> map) {
        param = map;
    }

    public void addParameter(String key, String value) {
        if (param == null)
            param = new HashMap<String, String>();
        param.put(key, value);
    }

    public void post() throws ClientProtocolException, IOException {
        HttpPost http = new HttpPost(url);
        setEntity(http);
        execute(http);
    }

    public void put() throws ClientProtocolException, IOException {
        HttpPut http = new HttpPut(url);
        setEntity(http);
        execute(http);
    }

    public void get() throws ClientProtocolException, IOException {
        if (param != null) {
            StringBuilder url = new StringBuilder(this.url);
            boolean isFirst = true;
            for (String key : param.keySet()) {
                if (isFirst)
                    url.append("?");
                else
                    url.append("&");
                url.append(key).append("=").append(param.get(key));
            }
            this.url = url.toString();
        }
        HttpGet http = new HttpGet(url);
        execute(http);
    }

    /**
     * set http post,put param
     */
    private void setEntity(HttpEntityEnclosingRequestBase http) {
        if (param != null) {
            List<NameValuePair> nvps = new LinkedList<NameValuePair>();
            for (String key : param.keySet())
                nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
            http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
        }
        if (xmlParam != null) {
            http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
        }
    }

    private void execute(HttpUriRequest http) throws ClientProtocolException,
            IOException {
        CloseableHttpClient httpClient = null;
        try {
            if (isHttps) {
                SSLContext sslContext = new SSLContextBuilder()
                        .loadTrustMaterial(null, new TrustStrategy() {
                            // 信任所有
                            public boolean isTrusted(X509Certificate[] chain,
                                    String authType)
                                    throws CertificateException {
                                return true;
                            }
                        }).build();
                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                        sslContext);
                httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
                        .build();
            } else {
                httpClient = HttpClients.createDefault();
            }
            CloseableHttpResponse response = httpClient.execute(http);
            try {
                if (response != null) {
                    if (response.getStatusLine() != null)
                        statusCode = response.getStatusLine().getStatusCode();
                    HttpEntity entity = response.getEntity();
                    // 响应内容
                    content = EntityUtils.toString(entity, Consts.UTF_8);
                }
            } finally {
                response.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            httpClient.close();
        }
    }

    public int getStatusCode() {
        return statusCode;
    }

    public String getContent() throws ParseException, IOException {
        return content;
    }

}

猜你喜欢

转载自blog.csdn.net/qq_42806915/article/details/82593155