1. 内网穿透完整测试程序 180
我们使用ngrok,直接注册一个账号开通隧道即可,简单方便
KuaiQianService 181
micr-pay

//服务器接收支付结果的后台地址,该参数务必填写,不能为空。
//String bgUrl = "http://localhost:9000/pay/kq/rece/notify";
String bgUrl = "http://oayunshang3.v1.idcfengye.com/pay/kq/rece/notify";//内网穿透地址   181KuaiQianController 181
micr-pay
//接收快钱给商家的支付结果 , 快钱以get方式,发送请求给商家  170
    @GetMapping("/rece/notify")
    @ResponseBody
    public String payResultNotify(HttpServletRequest request){
        System.out.println("==============接收快钱的异步通知============");//181
        kQService.kqNotify(request);
        return "<result>1</result><redirecturl>http://localhost:8080/</redirecturl>";
    }测试 181
启动micr-datasevice、micr-web、micr-pay
运行ngrok内网穿透
启动redis
启动zookeeper

测试我们用到的信息
测试银行卡:
可用的银行卡:
6214 6800 3838 7096 北京银行的
姓名:张峰
身份证  110101199105149839
测试环境:不对卡,姓名,身份证做检查, 正式环境会检查。支付一分钱,点击支付

跳转到快钱支付界面,更换信息



点击返回商家

成功

看看后台输出,没问题

看看数据库资金表,增加了一分钱

充值记录表也没问题

再次进入用户中心,查询到一条充值记录

至此我们整个支付程序,成功
2. 补单处理 182

补单查询快钱响应的数据格式
我们主要是获取这部分数据

{
 "recordCount": "30",
 "pageCount": "1",
 "orderDetail": [
         {
         "orderId": "1642244020946593",
         "orderAmount": 3,
         "orderTime": "20120605012740",
         "dealTime": "20120605013104",
         "payResult": "10",
         "payType": "10",
         "payAmount": 3,
         "fee": 1,
         "dealId": "23023847",
         "signInfo": 
        "jGf9%2F7vKOUkK%2B93ZcTxT9IOFoEhxSEDHmwKbdtEJuEHd4DhPU3BlotuujtHIHlaEPHp48t8c0uj78TzD
        M%2FUQeaH15o5DNmdRencMD0mo%2FmSRNEHtqgOZfY3H3VR4SbYUDgIf7YVAspBoK3re3feroGGUA8%2BKue8
        %2BgzPOjp%2Fgkkeh4GuA4KAAnunTuxELc82rsP0CuosawodILiHTvDwoyF1EgAIQqEChgdHbpQUtiCevluYK
        ORmwZJlN804L8GPmobMZXr4CVtVTYcGuOIUXzz4ImXEnfesCP2f6BOoJD14SIxU%2BrWHppByEKtKodUCJ7FA
        0pxCB6uqHUE3eNHuw1Q%3D%3D"
         }
     ],
     "signMsg": 
     "f3oEXOdTflGvJScrHZbjQQgz3RTwSnLTvHTJn0GUHcAAIjnXfN2rV79gWbEwRExkFfFq%2FEXl29%2Fw5ID3
     cfalx4hGqktXARUqf0WYKTz%2B3Un8%2Bv2vtcMXzfQAopKnPSAawZJ2CTWwSq9xkWxlTZj%2B7%2Bj1n9maa
     dCAVwsaNUFZN%2FT4t%2BqqMsI8wAfk%2FFcSwcwo2CDr%2F%2Bp2WQ3sQbVYDBd4hFa3s3Q24zeH9u7x9cZp
     2gNS7UdERTe7ksBjuhImnoJahNOP5WIzxZaXNpDFNwa0qjRhVFzMs97T7aa8M4LOhaI%2F8mesYGa8D50VlvY
     8GtApr%2F3CebFKRf8uU1XGqfZsXA%3D%3D",
     "signType": 2,
     "pageSize": "30",
     "merchantAcctId": "1001162931901",
     "errCode": "",
  "currentPage": "1", 
  "version": "v2.0"
}2.1 业务接口实现类KuaiQianService 183
工具类HttpUtil 183
micr-pay
资源在E:\java学习\盈利宝\资料\资料\10-快钱支付\人民币网关\DEMO\JAVA\人民币网关支付、退款查询\HttpQuery\src\com\query\Test

package com.bjpowernode.util;
import java.net.SocketTimeoutException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
/**
 * HttpUtil 工具类
 */
public class HttpUtil {
    private static final Log LOGGER = LogFactory.getLog(HttpUtil.class);
    private static PoolingHttpClientConnectionManager connManager;
    private static RequestConfig requestConfig;
    static{
        try {
            SSLContext sslcontext = createIgnoreVerifySSL();
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, null, null);
            SSLContext.setDefault(sslContext);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
                    .<ConnectionSocketFactory> create().register("http", PlainConnectionSocketFactory.INSTANCE)
                    .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
            connManager = new PoolingHttpClientConnectionManager(
                    socketFactoryRegistry);
            // 连接池超时时间使用connect超时时间
            requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(1000)
                    .setConnectTimeout(1000)
                    .setSocketTimeout(5000).build();
        } catch (Exception e) {
            LOGGER.error(" [XPAY-SDK] init connectionManager or requestConfig error !!! ",e);
            e.printStackTrace();
        }
    }
    public static String doPostJsonRequest(String reqeustString, String url,
                                           int connectTimeout, int socketTimeOut) throws Exception {
        CloseableHttpResponse response = null;
        try {
            changeRequestConfig(connectTimeout,socketTimeOut);
            CloseableHttpClient httpclient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
            httpPost.setConfig(requestConfig);
            httpPost.setEntity(new StringEntity(reqeustString, ContentType.APPLICATION_JSON));
            response = httpclient.execute(httpPost);
            // get http status code
            int resStatu = response.getStatusLine().getStatusCode();
            String responseString = null;
            if (resStatu == HttpStatus.SC_OK) {
                responseString = EntityUtils.toString(response.getEntity());
            } else {
                throw new Exception(url + ",the statusCode is " + resStatu);
            }
            return responseString;
        } catch (ConnectTimeoutException e) {
            e.printStackTrace();
        } catch (SocketTimeoutException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (response != null) {
                try {
                    EntityUtils.consume(response.getEntity());
                    response.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return url;
    }
    /**
     * 绕过验证
     *
     * @return
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    private static SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        // 实现一个X509TrustManager接口,用于绕过验证,不用修改里面的方法
        X509TrustManager trustManager = new X509TrustManager() {
            public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                                           String paramString) throws CertificateException {}
            public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                                           String paramString) throws CertificateException {}
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        sc.init(null, new TrustManager[] { trustManager }, null);
        return sc;
    }
    public static String doPostJsonRequestByHttps(String reqeustString, String url,
                                                  int connectTimeout, int socketTimeOut) {
        long startTime = System.currentTimeMillis();
        CloseableHttpResponse response = null;
        String responseString = null;
        try {
            changeRequestConfig(connectTimeout,socketTimeOut);
            CloseableHttpClient httpsClient = HttpClients.custom().setConnectionManager(connManager).build();
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
            httpPost.setConfig(requestConfig);
            httpPost.setEntity(new StringEntity(reqeustString, ContentType.APPLICATION_JSON));
            response = httpsClient.execute(httpPost);
            // get http status code
            int resStatu = response.getStatusLine().getStatusCode();
            responseString = null;
            if (resStatu == HttpStatus.SC_OK) {
                responseString = EntityUtils.toString(response.getEntity());
            } else {
                throw new Exception(url + ",the statusCode is " + resStatu);
            }
            LOGGER.info(String.format("response data : [ %s ] , time consuming : [ %s ] ms !! ",responseString
                    ,(System.currentTimeMillis()- startTime)));
            return responseString;
        }catch (ConnectTimeoutException e) {
            e.printStackTrace();
        } catch (SocketTimeoutException e) {
            e.printStackTrace();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (response != null) {
                try {
                    EntityUtils.consume(response.getEntity());
                    response.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return responseString;
    }
    /**
     * 修改默认超时时间
     * @param connectionTime
     * @param soTimeout
     */
    private static void changeRequestConfig(int connectionTime,int soTimeout){
        if(connectionTime != requestConfig.getConnectionRequestTimeout()
                || soTimeout != requestConfig.getSocketTimeout()){
            requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(connectionTime)
                    .setConnectTimeout(connectionTime)
                    .setSocketTimeout(soTimeout).build();
        }
    }
}KuaiQianService 183
micr-pay
//补单操作  调用快钱的查询接口   183
    public void handleQueryOrder() {
        //1.从redis获取订单号
        //range(0, -1)代表取所有
        Set<String> orders = stringRedisTemplate
                .boundZSetOps(RedisKey.KEY_ORDERID_SET).range(0, -1);
        //2.循环订单号
        orders.forEach( orderId -> {
            //3.每个订单,调用查询接口
            doQuery(orderId);
        });
    }
    //查询接口  183
    private void doQuery(String orderId){
        Map<String, Object> request = new HashMap<String, Object>();
        //固定值:1代表UTF-8;
        String inputCharset = "1";
        //固定值:v2.0 必填
        String version = "v2.0";
        //1代表Md5,2 代表PKI加密方式  必填
        String signType = "2";
        //人民币账号 membcode+01  必填
        String merchantAcctId = "1001214035601";
        //固定值:0 按商户订单号单笔查询,1 按交易结束时间批量查询必填
        String queryType = "0";
        //固定值:1	代表简单查询 必填
        String queryMode = "1";
        //数字串,格式为:年[4 位]月[2 位]日[2 位]时[2 位]分[2 位]秒[2位],例如:20071117020101
        String startTime = "";
        ////数字串,格式为:年[4 位]月[2 位]日[2 位]时[2 位]分[2 位]秒[2位],例如:20071117020101
        String endTime = "";
        String requestPage = "";
        String key = "27YKWKBKHT2IZSQ4";
        request.put("inputCharset", inputCharset);
        request.put("version", version);
        request.put("signType", signType);
        request.put("merchantAcctId", merchantAcctId);
        request.put("queryType", queryType);
        request.put("queryMode", queryMode);
        request.put("startTime", startTime);
        request.put("endTime", endTime);
        request.put("requestPage", requestPage);
        request.put("orderId", orderId);
        String message="";
        message = appendParam(message,"inputCharset",inputCharset,null);
        message = appendParam(message,"version",version,null);
        message = appendParam(message,"signType",signType,null);
        message = appendParam(message,"merchantAcctId",merchantAcctId,null);
        message = appendParam(message,"queryType",queryType,null);
        message = appendParam(message,"queryMode",queryMode,null);
        message = appendParam(message,"startTime",startTime,null);
        message = appendParam(message,"endTime",endTime,null);
        message = appendParam(message,"requestPage",requestPage,null);
        message = appendParam(message,"orderId",orderId,null);
        message = appendParam(message,"key",key,null);
        Pkipair pki = new Pkipair();
        String sign = pki.signMsg(message);
        request.put("signMsg", sign);
        System.out.println("请求json串===" + JSON.toJSONString(request));
        //sandbox提交地址
        String reqUrl = "https://sandbox.99bill.com/gatewayapi/gatewayOrderQuery.do";
        String response = "";
        try {
            response = HttpUtil.doPostJsonRequestByHttps(JSON.toJSONString(request), reqUrl, 3000, 8000);
            //解析response   183-184
            if(StringUtils.isNotBlank(response)){
                Object detailObject = JSONObject.parseObject(response).get("orderDetail");
                System.out.println("detailObject===="+detailObject);
                if( detailObject != null){
                    //把查询的Object转为JSONArray
                    //解释orderDetail部分数据,本身是一个数组被放在了json中
                    ///我们将得到的detailObject对象转为json的数组形式
                    JSONArray array = (JSONArray)detailObject;
                    //取出json数组的第一个元素   184
                    JSONObject detailJsonObject = array.getJSONObject(0);
                    if( detailJsonObject != null){
                        //处理充值结果,和异步通知一样   184
                        int result = rechargeService.handleKQNotify(
                                detailJsonObject.getString("orderId"),//订单号
                                detailJsonObject.getString("payAmount"),//实付金额
                                detailJsonObject.getString("payResult")//支付结果
                        );
                        System.out.println("处理的订单号是:"+orderId+",处理结果:"+result);
                    }
                }
            }
            //删除redis中的订单   184
            stringRedisTemplate.boundZSetOps(RedisKey.KEY_ORDERID_SET).remove(orderId);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
    }2.2 控制controller 183
KuaiQianController
micr-pay
//补单操作  183
    //从定时任务,调用接口  183
    @GetMapping("/rece/query")
    @ResponseBody
    public String queryKQOrder(){
        kQService.handlerQueryOrder();
        return "接收了查询的请求";
    }2.3 在定时任务中掉用 184
TaskManager 184
micr-task
//调用查询补单接口  184
    @Scheduled(cron = "0 0/20 * * * ?")//每隔20分钟查询一次
    public void invokeKuaiQianQuery(){
        try {
            String url = "http://localhost:9000/pay/kq/rece/query";
            HttpClientUtils.doGet(url);
        }catch (Exception e){
            e.printStackTrace();
        }
    }启动类 184
micr-task

2.4 测试 185
为了方便测试我们从数据库中拷贝一个已经处理过的订单

将订单号拷贝进redis


启动micr-datasevice、micr-web、micr-task、debug启动micr-pay
运行ngrok内网穿透
启动redis
启动zookeeper
在程序遍历第一个订单时也就是红框,因为这个数据没有向快钱发起过支付请求,所以补单查询响应回来的数据orderDetail为空

当遍历到尾号819时,因为这个订单号是之前处理的,所以

处理结果为3证明已经处理过了,所以即便是orderDetail有数据,也不会处理


看看redis,存储订单的redis消失了,成功(因为我们程序设计的即使从redis去一个进行查询补单,就删除一个)

看看数据库819数据没有任何影响

资金表,资金也没有变化,成功

至此,本项目完结
3. 总结 186

4. 项目总体测试
4.1 启动redis

4.2 启动zookeeper

4.3 首先启动micr-dataservice、micr-web、micr-pay

4.4 启动内网穿透
(注意自己内网穿透使用的地址,要注意内网过期,过期的话就不要使用内网穿透了,去程序汇中修改KuaiQianService类的generateFormData的bgUrl属性就是了)

4.5 启动前端

4.6 测试
浏览器输入http://localhost:8080/

后续测试不在演示










