总览:
在pay模块util包下,创建签名工具类Pkipair和http工具类HttpUtil:
package com.bjpowernode.util;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
public class Pkipair {
/*生成签名*/
public String signMsg( String signMsg) {
String base64 = "";
try {
KeyStore ks = KeyStore.getInstance("PKCS12");
//商户私钥文件
String file = Pkipair.class.getResource("10012140356.pfx").getPath().replaceAll("%20", " ");
System.out.println(file);
FileInputStream ksfis = new FileInputStream(file);
BufferedInputStream ksbufin = new BufferedInputStream(ksfis);
char[] keyPwd = "123456".toCharArray();
ks.load(ksbufin, keyPwd);
PrivateKey priK = (PrivateKey) ks.getKey("test-alias", keyPwd);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(priK);
signature.update(signMsg.getBytes("utf-8"));
sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
base64 = encoder.encode(signature.sign());
} catch(FileNotFoundException e){
e.printStackTrace();
}catch (Exception ex) {
ex.printStackTrace();
}
return base64;
}
/*验签的方法*/
public boolean enCodeByCer( String val, String msg) {
boolean flag = false;
try {
//快钱的公钥文件
String file = Pkipair.class.getResource("99bill[1].cert.rsa.20140803.cer").toURI().getPath();
System.out.println(file); // 99bill.cert.rsa.20140803.cer
FileInputStream inStream = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
PublicKey pk = cert.getPublicKey();
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(pk);
signature.update(val.getBytes());
sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
System.out.println(new String(decoder.decodeBuffer(msg)));
flag = signature.verify(decoder.decodeBuffer(msg));
System.out.println(flag);
} catch (Exception e) {
e.printStackTrace();
System.out.println("no");
}
return flag;
}
}
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();
}
}
}
在pay模块service包下,创建KuaiQianService:
1、获取用户信息(queryUser)
2、生成快钱支付接口的数据 Map是发送给快钱的所有请求参数(generateFormData)
3、创建充值记录(addRecharge)
4、生成订单号orderId(generateOrderId)
5、把订单号,存放到redis(addOrderIdToRedis)
6、处理异步通知(kqNotify)
7、调用快钱的查询接口(handleQueryOrder)
package com.bjpowernode.pay.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bjpowernode.api.model.RechargeRecord;
import com.bjpowernode.api.model.User;
import com.bjpowernode.api.service.RechargeService;
import com.bjpowernode.api.service.UserService;
import com.bjpowernode.common.constants.RedisKey;
import com.bjpowernode.common.constants.YLBConstant;
import com.bjpowernode.util.HttpUtil;
import com.bjpowernode.util.Pkipair;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Service
public class KuiQianService {
@DubboReference(interfaceClass = UserService.class,version = "1.0")
private UserService userService;
@DubboReference(interfaceClass = RechargeService.class,version = "1.0")
private RechargeService rechargeService;
@Resource
private StringRedisTemplate stringRedisTemplate;
/*获取用户信息*/
public User queryUser(Integer uid){
User user = userService.queryById(uid);
return user;
}
/*生成快钱支付接口的数据 Map是发送给快钱的所有请求参数*/
public Map<String,String> generateFormData(Integer uid, String phone, BigDecimal rechargeMoney) {
Map<String,String> data = new HashMap<>();
//人民币网关账号,该账号为11位人民币网关商户编号+01,该参数必填。
String merchantAcctId = "1001214035601";//
//编码方式,1代表 UTF-8; 2 代表 GBK; 3代表 GB2312 默认为1,该参数必填。
String inputCharset = "1";
//接收支付结果的页面地址,该参数一般置为空即可。
String pageUrl = "";
//服务器接收支付结果的后台地址,该参数务必填写,不能为空。
String bgUrl = "http://451627p2o2.qicp.vip/pay/kq/rece/notify";
//网关版本,固定值:v2.0,该参数必填。
String version = "v2.0";
//语言种类,1代表中文显示,2代表英文显示。默认为1,该参数必填。
String language = "1";
//签名类型,该值为4,代表PKI加密方式,该参数必填。
String signType = "4";
//支付人姓名,可以为空。
String payerName= "";
//支付人联系类型,1 代表电子邮件方式;2 代表手机联系方式。可以为空。
String payerContactType = "2";
//支付人联系方式,与payerContactType设置对应,payerContactType为1,则填写邮箱地址;payerContactType为2,则填写手机号码。可以为空。
String payerContact = phone;
//指定付款人,可以为空
String payerIdType = "3";
//付款人标识,可以为空
String payerId = String.valueOf(uid);
//付款人IP,可以为空
String payerIP = "";
//商户订单号,以下采用时间来定义订单号,商户可以根据自己订单号的定义规则来定义该值,不能为空。
String orderId = "KQ"+generateOrderId();
//订单金额,金额以“分”为单位,商户测试以1分测试即可,切勿以大金额测试。该参数必填。
//0.01 * 100 == 1.00 100000*100 = 10000000 , 科学计数法 1 * 10^7
String orderAmount = rechargeMoney.multiply(new BigDecimal("100")).stripTrailingZeros().toPlainString();
//订单提交时间,格式:yyyyMMddHHmmss,如:20071117020101,不能为空。
String orderTime = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date());
//快钱时间戳,格式:yyyyMMddHHmmss,如:20071117020101, 可以为空
String orderTimestamp = orderTime;
//商品名称,可以为空。
String productName= "动力理财产品";
//商品数量,可以为空。
String productNum = "1";
//商品代码,可以为空。
String productId = "10000";
//商品描述,可以为空。
String productDesc = "购买产品";
//扩展字段1,商户可以传递自己需要的参数,支付完快钱会原值返回,可以为空。
String ext1 = "";
//扩展自段2,商户可以传递自己需要的参数,支付完快钱会原值返回,可以为空。
String ext2 = "";
//支付方式,一般为00,代表所有的支付方式。如果是银行直连商户,该值为10-1或10-2,必填。
String payType = "00";
//银行代码,如果payType为00,该值可以为空;如果payType为10-1或10-2,该值必须填写,具体请参考银行列表。
String bankId = "";
//同一订单禁止重复提交标志,实物购物车填1,虚拟产品用0。1代表只能提交一次,0代表在支付不成功情况下可以再提交。可为空。
String redoFlag = "0";
//快钱合作伙伴的帐户号,即商户编号,可为空。
String pid = "";
// signMsg 签名字符串 不可空,生成加密签名串
String signMsgVal = "";
signMsgVal = appendParam(signMsgVal, "inputCharset", inputCharset,data);
signMsgVal = appendParam(signMsgVal, "pageUrl", pageUrl,data);
signMsgVal = appendParam(signMsgVal, "bgUrl", bgUrl,data);
signMsgVal = appendParam(signMsgVal, "version", version,data);
signMsgVal = appendParam(signMsgVal, "language", language,data);
signMsgVal = appendParam(signMsgVal, "signType", signType,data);
signMsgVal = appendParam(signMsgVal, "merchantAcctId",merchantAcctId,data);
signMsgVal = appendParam(signMsgVal, "payerName", payerName,data);
signMsgVal = appendParam(signMsgVal, "payerContactType",payerContactType,data);
signMsgVal = appendParam(signMsgVal, "payerContact", payerContact,data);
signMsgVal = appendParam(signMsgVal, "payerIdType", payerIdType,data);
signMsgVal = appendParam(signMsgVal, "payerId", payerId,data);
signMsgVal = appendParam(signMsgVal, "payerIP", payerIP,data);
signMsgVal = appendParam(signMsgVal, "orderId", orderId,data);
signMsgVal = appendParam(signMsgVal, "orderAmount", orderAmount,data);
signMsgVal = appendParam(signMsgVal, "orderTime", orderTime,data);
signMsgVal = appendParam(signMsgVal, "orderTimestamp", orderTimestamp,data);
signMsgVal = appendParam(signMsgVal, "productName", productName,data);
signMsgVal = appendParam(signMsgVal, "productNum", productNum,data);
signMsgVal = appendParam(signMsgVal, "productId", productId,data);
signMsgVal = appendParam(signMsgVal, "productDesc", productDesc,data);
signMsgVal = appendParam(signMsgVal, "ext1", ext1,data);
signMsgVal = appendParam(signMsgVal, "ext2", ext2,data);
signMsgVal = appendParam(signMsgVal, "payType", payType,data);
signMsgVal = appendParam(signMsgVal, "bankId", bankId,data);
signMsgVal = appendParam(signMsgVal, "redoFlag", redoFlag,data);
signMsgVal = appendParam(signMsgVal, "pid", pid,data);
System.out.println(signMsgVal);
Pkipair pki = new Pkipair();
//生成签名串
String signMsg = pki.signMsg(signMsgVal);
//需要signMsg
data.put("signMsg",signMsg);
return data;
}
/*创建充值记录*/
public boolean addRecharge(Integer uid, BigDecimal rechargeMoney, String orderId) {
RechargeRecord record = new RechargeRecord();
record.setChannel("KQ");
record.setRechargeDesc("快钱充值");
record.setRechargeMoney(rechargeMoney);
record.setRechargeNo(orderId);
record.setRechargeStatus(YLBConstant.RECHARGE_STATUS_PROCESSING);
record.setRechargeTime(new Date());
record.setUid(uid);
int rows = rechargeService.addRechargeRecord(record);
return rows > 0;
}
/**************辅助工具方法***********************************/
private String appendParam(String returns, String paramId, String paramValue,Map<String,String> data) {
if (returns != "") {
if (paramValue != "" && paramValue != null) {
returns += "&" + paramId + "=" + paramValue;
}
} else {
if (paramValue != "" && paramValue != null) {
returns = paramId + "=" + paramValue;
}
}
if( data != null ){
data.put(paramId,paramValue);
}
return returns;
}
//生成orderId
private String generateOrderId(){
//唯一值。1. 使用数据库的自增主键
// 2. 使用redis的自增
// orderId = 时间戳 + redis的自增
String key = RedisKey.KEY_RECHARGE_ORDERID;
Long incr = stringRedisTemplate.boundValueOps(key).increment();
String orderId = DateFormatUtils.format(new Date(),"yyyyMMddHHmmssSSS") + incr;
return orderId;
}
/*把订单号,存放到redis*/
public void addOrderIdToRedis(String orderId) {
String key = RedisKey.KEY_ORDERID_SET;
stringRedisTemplate.boundZSetOps(key).add(orderId, new Date().getTime());
}
/*处理异步通知*/
public void kqNotify(HttpServletRequest request) {
String merchantAcctId = request.getParameter("merchantAcctId");
String version = request.getParameter("version");
String language = request.getParameter("language");
String signType = request.getParameter("signType");
String payType = request.getParameter("payType");
String bankId = request.getParameter("bankId");
String orderId = request.getParameter("orderId");
String orderTime = request.getParameter("orderTime");
String orderAmount = request.getParameter("orderAmount");
String bindCard = request.getParameter("bindCard");
if(request.getParameter("bindCard")!=null){
bindCard = request.getParameter("bindCard");}
String bindMobile="";
if(request.getParameter("bindMobile")!=null){
bindMobile = request.getParameter("bindMobile");}
String dealId = request.getParameter("dealId");
String bankDealId = request.getParameter("bankDealId");
String dealTime = request.getParameter("dealTime");
String payAmount = request.getParameter("payAmount");
String fee = request.getParameter("fee");
String ext1 = request.getParameter("ext1");
String ext2 = request.getParameter("ext2");
String payResult = request.getParameter("payResult");
String aggregatePay = request.getParameter("aggregatePay");
String errCode = request.getParameter("errCode");
String signMsg = request.getParameter("signMsg");
//拼接签名计算的串
String merchantSignMsgVal = "";
merchantSignMsgVal = appendParam(merchantSignMsgVal,"merchantAcctId", merchantAcctId,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "version",version,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "language",language,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "signType",signType,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "payType",payType,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "bankId",bankId,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "orderId",orderId,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "orderTime",orderTime,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "orderAmount",orderAmount,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "bindCard",bindCard,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "bindMobile",bindMobile,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "dealId",dealId,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "bankDealId",bankDealId,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "dealTime",dealTime,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "payAmount",payAmount,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "fee", fee,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "ext1", ext1,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "ext2", ext2,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "payResult",payResult,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "aggregatePay",aggregatePay,null);
merchantSignMsgVal = appendParam(merchantSignMsgVal, "errCode",errCode,null);
Pkipair pki = new Pkipair();
boolean flag = pki.enCodeByCer(merchantSignMsgVal, signMsg);
System.out.println("flag==="+flag);
if( flag ){
/**
* 可以处理业务逻辑
* 1.判断商户是商家自己的吗
* 2.判断订单在商家是否存在,是否处理过
* 3.判断金额是否一致
* 4.如果成功,更新用户的资金
* 5.修改充值表的记录状态
* 6.删除redis中的处理过的订单号
*/
if("1001214035601".equals(merchantAcctId)){
int rechargeResult = rechargeService.handleKQNotify(orderId,payAmount,payResult);
System.out.println("订单"+orderId+",充值处理结果:"+rechargeResult);
} else {
System.out.println("订单"+orderId+",充值处理结果:商家号不正确");
}
} else {
System.out.println("订单"+orderId+"验签失败,不能处理");
}
//删除redis中的订单记录
stringRedisTemplate.boundZSetOps(RedisKey.KEY_ORDERID_SET).remove(orderId);
}
/*调用快钱的查询接口*/
public void handleQueryOrder() {
//1.从redis获取订单号
Set<String> orders = stringRedisTemplate
.boundZSetOps(RedisKey.KEY_ORDERID_SET).range(0, -1);
//2.循环订单号
orders.forEach( orderId -> {
//3.每个订单,调用查询接口
doQuery(orderId);
});
}
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
if(StringUtils.isNotBlank(response)){
Object detailObject = JSONObject.parseObject(response).get("orderDetail");
System.out.println("detailObject===="+detailObject);
if( detailObject != null){
//把查询的Object转为JSONArray
JSONArray array = (JSONArray)detailObject;
JSONObject detailJsonObject = array.getJSONObject(0);
if( detailJsonObject != null){
//处理充值结果,和异步通知一样
int result = rechargeService.handleKQNotify(
detailJsonObject.getString("orderId"),
detailJsonObject.getString("payAmount"),
detailJsonObject.getString("payResult")
);
System.out.println("处理的订单号是:"+orderId+",处理结果:"+result);
}
}
}
//删除redis中的订单
stringRedisTemplate.boundZSetOps(RedisKey.KEY_ORDERID_SET).remove(orderId);
} catch (Exception e) {
e.printStackTrace();
return;
}
}
}
其中:
1、在api模块下的service包,充值接口RechargeService添加:(创建充值记录方法addRechargeRecord 和 处理后续充值方法handleKQNotify)
package com.bjpowernode.api.service;
import com.bjpowernode.api.model.RechargeRecord;
import java.util.List;
/**
* 充值接口
*/
public interface RechargeService {
/*根据userID查询它的充值记录*/
List<RechargeRecord> queryByUid(Integer uid,Integer pageNo, Integer pageSize);
/*创建充值记录*/
int addRechargeRecord(RechargeRecord record);
/*处理后续充值*/
int handleKQNotify(String orderId, String payAmount, String payResult);
}
2、实现这个接口方法,在dataservice模块service包下,实现类RechargeServiceImpl添加:(创建充值记录addRechargeRecord 和 处理后续充值handleKQNotify):
package com.bjpowernode.dataservice.service;
import com.bjpowernode.api.model.RechargeRecord;
import com.bjpowernode.api.service.RechargeService;
import com.bjpowernode.common.constants.YLBConstant;
import com.bjpowernode.common.util.CommonUtil;
import com.bjpowernode.dataservice.mapper.FinanceAccountMapper;
import com.bjpowernode.dataservice.mapper.RechargeRecordMapper;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
@DubboService(interfaceClass = RechargeService.class,version = "1.0")
public class RechargeServiceImpl implements RechargeService {
@Resource
private RechargeRecordMapper rechargeMapper;
@Resource
private FinanceAccountMapper accountMapper;
/*根据userID查询它的充值记录*/
@Override
public List<RechargeRecord> queryByUid(Integer uid, Integer pageNo, Integer pageSize) {
List<RechargeRecord> records = new ArrayList<>();
if( uid != null && uid > 0 ){
pageNo = CommonUtil.defaultPageNo(pageNo);
pageSize = CommonUtil.defaultPageSize(pageSize);
int offset = (pageNo -1 ) * pageSize;
records = rechargeMapper.selectByUid(uid, offset, pageSize);
}
return records;
}
/*创建充值记录*/
@Override
public int addRechargeRecord(RechargeRecord record) {
return rechargeMapper.insertSelective(record);
}
/*处理后续充值*/
@Transactional(rollbackFor = Exception.class)
@Override
public synchronized int handleKQNotify(String orderId, String payAmount, String payResult) {
int result = 0;//订单不存在
int rows = 0;
//1.查询订单
RechargeRecord record = rechargeMapper.selectByRechargeNo(orderId);
if(record != null ){
if( record.getRechargeStatus() == YLBConstant.RECHARGE_STATUS_PROCESSING){
//2.判断金额是否一致
String fen = record.getRechargeMoney().multiply(new BigDecimal("100"))
.stripTrailingZeros().toPlainString();
if( fen.equals(payAmount)){
//金额一致
if("10".equals(payResult)){
//成功
rows = accountMapper.updateAvailableMoneyByRecharge(record.getUid(),record.getRechargeMoney());
if(rows < 1 ){
throw new RuntimeException("充值更新资金账号失败");
}
//更新充值记录的状态
rows = rechargeMapper.updateStatus(record.getId(),YLBConstant.RECHARGE_STATUS_SUCCESS);
if( rows < 1) {
throw new RuntimeException("充值更新充值记录状态失败");
}
result = 1;//成功
} else {
//充值失败
//更新充值记录的状态
rows = rechargeMapper.updateStatus(record.getId(),YLBConstant.RECHARGE_STATUS_FAIL);
if( rows < 1) {
throw new RuntimeException("充值更新充值记录状态失败");
}
result = 2;//充值结果是失败的
}
} else {
result = 4;//金额不一样
}
} else {
result = 3;//订单已经处理过了
}
}
return result;
}
}
其中:
1、根据订单号,查询记录selectByRechargeNo(需要在dataservice模块mapper包下的RechargeRecordMapper接口添加方法,并在resources/mappers/RechargeRecordMapper.xml编写SQL语句):
RechargeRecord selectByRechargeNo(@Param("rechargeNo") String orderId);
<!--根据订单号,查询记录-->
<select id="selectByRechargeNo" resultMap="BaseResultMap">
select <include refid="Base_Column_List" />
from b_recharge_record
where recharge_no = #{rechargeNo} for update
</select>
2、充值更新金额updateAvailableMoneyByRecharge(需要在dataservice模块mapper包下的FinanceAccountMapper接口添加方法,并在resources/mappers/FinanceAccountMapper.xml编写SQL语句):
/*充值更新金额*/
int updateAvailableMoneyByRecharge(@Param("uid") Integer uid, @Param("rechargeMoney") BigDecimal rechargeMoney);
<!--充值更新金额-->
<update id="updateAvailableMoneyByRecharge">
update u_finance_account set available_money = available_money + #{rechargeMoney}
where uid = #{uid}
</update>
3、更新充值记录状态updateStatus(需要在dataservice模块mapper包下的RechargeRecordMapper接口添加方法,并在resources/mappers/RechargeRecordMapper.xml编写SQL语句):
/*更新状态*/
int updateStatus(@Param("id") Integer id, @Param("newStatus") int rechargeStatusSuccess);
<!--更新状态-->
<update id="updateStatus">
update b_recharge_record set recharge_status = #{newStatus} where id = #{id}
</update>
在pay模块controller包下,创建接受请求的KuaiQianController:
一、(接收来自vue项目的支付充值请求)
1、检查uid是否是有效的用户(用户存在,且充值金额充足),并查询用户(kQService.queryUser(uid))
2、创建快钱支付接口需要的请求参数(kQService.generateFormData(uid,user.getPhone(),rechargeMoney))
3、创建充值记录(kQService.addRecharge(uid,rechargeMoney,data.get(“orderId”)))
4、把订单号存放到redis(kQService.addOrderIdToRedis(data.get(“orderId”)))
5、提交支付请求给快钱的form页面(thymeleaf)(view = “kqForm”;)
二、(接收快钱给商家的支付结果 , 快钱以get方式,发送请求给商家)
三、从定时任务,调用的接口
package com.bjpowernode.pay.controller;
import com.bjpowernode.api.model.User;
import com.bjpowernode.pay.service.KuiQianService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Map;
@Controller
@RequestMapping("/kq")
public class KuaiQianController {
@Resource
private KuiQianService kQService;
/*接收来自vue项目的支付充值请求*/
@GetMapping("/rece/recharge")
public String receFrontRechargeKQ(Integer uid,
BigDecimal rechargeMoney,
Model model){
//默认是错误视图
String view="err";
if(uid != null && uid > 0 &&
rechargeMoney != null && rechargeMoney.doubleValue() > 0 ){
//1.检查uid是否是有效的用户
try{
User user = kQService.queryUser(uid);
if(user != null ){
//创建快钱支付接口需要的请求参数
Map<String,String> data = kQService.generateFormData(uid,user.getPhone(),rechargeMoney);
model.addAllAttributes(data);
//创建充值记录
kQService.addRecharge(uid,rechargeMoney,data.get("orderId"));
//把订单号存放到redis
kQService.addOrderIdToRedis(data.get("orderId"));
//提交支付请求给快钱的form页面(thymeleaf)
view = "kqForm";
}
}catch (Exception e){
e.printStackTrace();
}
}
return view;
}
//接收快钱给商家的支付结果 , 快钱以get方式,发送请求给商家
@GetMapping("/rece/notify")
@ResponseBody
public String payResultNotify(HttpServletRequest request){
System.out.println("=================接收快钱的异步通知=============");
kQService.kqNotify(request);
return "<result>1</result><redirecturl>http://localhost:8080/</redirecturl>";
}
//从定时任务,调用的接口
@GetMapping("/rece/query")
@ResponseBody
public String queryKQOrder(){
kQService.handleQueryOrder();
return "接收了查询的请求";
}
}
其中:
1、在pay模块,resources/template下,创建两个页面:err.html和kqForm.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>请求错误</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form name="kqPay" action="https://sandbox.99bill.com/gateway/recvMerchantInfoAction.htm" method="get">
<input type="hidden" name="inputCharset" th:value="${inputCharset}" />
<input type="hidden" name="pageUrl" th:value="${pageUrl}" />
<input type="hidden" name="bgUrl" th:value="${bgUrl}" />
<input type="hidden" name="version" th:value="${version}" />
<input type="hidden" name="language" th:value="${language}" />
<input type="hidden" name="signType" th:value="${signType}" />
<input type="hidden" name="signMsg" th:value="${signMsg}" />
<input type="hidden" name="merchantAcctId" th:value="${merchantAcctId}" />
<input type="hidden" name="payerName" th:value="${payerName}" />
<input type="hidden" name="payerContactType" th:value="${payerContactType}" />
<input type="hidden" name="payerContact" th:value="${payerContact}" />
<input type="hidden" name="payerIdType" th:value="${payerIdType}" />
<input type="hidden" name="payerId" th:value="${payerId}" />
<input type="hidden" name="payerIP" th:value="${payerIP}" />
<input type="hidden" name="orderId" th:value="${orderId}" />
<input type="hidden" name="orderAmount" th:value="${orderAmount}" />
<input type="hidden" name="orderTime" th:value="${orderTime}" />
<input type="hidden" name="orderTimestamp" th:value="${orderTimestamp}" />
<input type="hidden" name="productName" th:value="${productName}" />
<input type="hidden" name="productNum" th:value="${productNum}" />
<input type="hidden" name="productId" th:value="${productId}" />
<input type="hidden" name="productDesc" th:value="${productDesc}" />
<input type="hidden" name="ext1" th:value="${ext1}" />
<input type="hidden" name="ext2" th:value="${ext2}" />
<input type="hidden" name="payType" th:value="${payType}" />
<input type="hidden" name="bankId" th:value="${bankId}" />
<input type="hidden" name="redoFlag" th:value="${redoFlag}" />
<input type="hidden" name="pid" th:value="${pid}" />
<!-- <input type="submit" name="submit" th:value="提交到快钱">-->
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>