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";//内网穿透地址 181
KuaiQianController 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/
后续测试不在演示