panxiaohe
2 years ago
15 changed files with 981 additions and 6 deletions
-
3cereshop-app/src/main/java/com/shop/cereshop/app/pay/PayFactory.java
-
6cereshop-app/src/main/java/com/shop/cereshop/app/pay/xs/service/XsPayService.java
-
228cereshop-app/src/main/java/com/shop/cereshop/app/pay/xs/service/impl/XsPayServiceImpl.java
-
134cereshop-app/src/main/java/com/shop/cereshop/app/pay/xs/utils/CommonUtils.java
-
60cereshop-app/src/main/java/com/shop/cereshop/app/pay/xs/utils/HttpUtils.java
-
67cereshop-app/src/main/java/com/shop/cereshop/app/pay/xs/utils/MD5Utils.java
-
291cereshop-app/src/main/java/com/shop/cereshop/app/pay/xs/utils/RSAUtils.java
-
49cereshop-app/src/main/java/com/shop/cereshop/app/service/order/impl/CereShopOrderServiceImpl.java
-
5cereshop-app/src/main/resources/application-app-dev.yml
-
10cereshop-app/src/main/resources/application.yml
-
4cereshop-business/src/main/java/com/shop/cereshop/business/pay/xs/service/XsPayService.java
-
23cereshop-commons/src/main/java/com/shop/cereshop/commons/config/PayConfig.java
-
104cereshop-commons/src/main/java/com/shop/cereshop/commons/config/XspayConfig.java
-
1cereshop-commons/src/main/java/com/shop/cereshop/commons/constant/IntegerEnum.java
-
2cereshop-commons/src/main/java/com/shop/cereshop/commons/utils/PayUtil.java
@ -0,0 +1,6 @@ |
|||
package com.shop.cereshop.app.pay.xs.service; |
|||
|
|||
import com.shop.cereshop.app.pay.PayService; |
|||
|
|||
public interface XsPayService extends PayService { |
|||
} |
@ -0,0 +1,228 @@ |
|||
package com.shop.cereshop.app.pay.xs.service.impl; |
|||
|
|||
import cn.hutool.core.util.StrUtil; |
|||
import com.alibaba.fastjson.JSONArray; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.alibaba.fastjson.TypeReference; |
|||
import com.shop.cereshop.app.pay.xs.service.XsPayService; |
|||
import com.shop.cereshop.app.pay.xs.utils.CommonUtils; |
|||
import com.shop.cereshop.app.pay.xs.utils.HttpUtils; |
|||
import com.shop.cereshop.commons.config.XspayConfig; |
|||
import com.shop.cereshop.commons.exception.CoBusinessException; |
|||
import com.sun.javafx.fxml.builder.URLBuilder; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.http.client.utils.URIBuilder; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.net.URI; |
|||
import java.net.URL; |
|||
import java.util.Base64; |
|||
import java.util.HashMap; |
|||
import java.util.LinkedHashMap; |
|||
import java.util.Map; |
|||
|
|||
@Service(value = "xsPayService") |
|||
@Slf4j(topic = "XsPayServiceImpl") |
|||
public class XsPayServiceImpl implements XsPayService { |
|||
|
|||
//支付申请服务代码 serCode |
|||
private static final String PAY_SER_CODE = "101005"; |
|||
|
|||
@Override |
|||
public Map<String, String> gotoPay(String orderFormid, BigDecimal money, String openid, String ip, Integer type, Integer huabeiPeriod) throws CoBusinessException, Exception { |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, String> refund(String transactionId, String outRefundNo, BigDecimal total, BigDecimal refund) throws CoBusinessException, Exception { |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, String> orderRefund(String transactionId, String outRefundNo, BigDecimal total, BigDecimal refund) throws CoBusinessException, Exception { |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, String> refundBond(String transactionId, String outRefundNo, BigDecimal total, BigDecimal refund) throws CoBusinessException, Exception { |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public String getCollectionCode(String orderFormid, BigDecimal money, String ip, String tradeType) throws CoBusinessException, Exception { |
|||
return null; |
|||
} |
|||
|
|||
//获取二维码 |
|||
@Override |
|||
public String getOrderCollectionCode(String orderFormid, BigDecimal money, String ip, String tradeType) throws CoBusinessException, Exception { |
|||
//构建请求参数 |
|||
Map<String, String> headMap = CommonUtils.buildHeadMap(PAY_SER_CODE); |
|||
|
|||
Map<String, Object> contentMap = buildPayContentMap(); |
|||
|
|||
//head+content做md5并根据加签方式加密 |
|||
String msg = CommonUtils.getRequestMsg(headMap,contentMap); |
|||
log.info("【" + PAY_SER_CODE + "】接口-请求报文:msg="+msg); |
|||
|
|||
String respMsg = HttpUtils.httpPost(XspayConfig.URL,msg); |
|||
//若响应respMsg为空时联系新生工作人员,此处因时收银台模式,前端页面提交方式,当时错误时,错误信息会重定向到其他页面展示 |
|||
log.info("【" + PAY_SER_CODE + "】接口-响应报文:" + respMsg); |
|||
|
|||
if(StrUtil.isNotBlank(respMsg)){ |
|||
try { |
|||
//解析响应报文并验签 |
|||
CommonUtils.verifySignRespMsg(PAY_SER_CODE,respMsg); |
|||
HashMap<String,String> responseMap = JSONObject.parseObject(respMsg,new TypeReference<HashMap<String,String>>(){}); |
|||
String resMsg = responseMap.get("msg"); |
|||
String responeMsg = new String(Base64.getDecoder().decode(resMsg)); |
|||
JSONObject jsonObject = JSONObject.parseObject(responeMsg); |
|||
String qrCodeUrl = jsonObject.getJSONObject("content").getJSONObject("payInfo").getString("qrCodeUrl"); |
|||
qrCodeUrl = new URIBuilder(qrCodeUrl).getQueryParams().get(0).getValue(); |
|||
return qrCodeUrl; |
|||
} catch (CoBusinessException e) { |
|||
throw e; |
|||
} catch (Exception e) { |
|||
//支付方式为B2C、B2B、H5,成功响应时respMsg为html页面代码 |
|||
if(respMsg.contains("toNativePayIndexForm")){ |
|||
log.info("【" + PAY_SER_CODE + "】接口-响应报文-HTML代码无需验签"); |
|||
}else{ |
|||
e.printStackTrace(); |
|||
} |
|||
throw new CoBusinessException("10000","获取支付二维码出错"); |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
|
|||
|
|||
/** |
|||
* 构建content请求参数 |
|||
* @return |
|||
*/ |
|||
private static Map<String,Object> buildPayContentMap(){ |
|||
// 设置请求正文 contentMap有序,顺序按照文档从上到下,为空字段可不传该字段 |
|||
Map<String, Object> contentMap = new LinkedHashMap<>(); |
|||
//商户订单号 |
|||
contentMap.put("merOrderId", System.currentTimeMillis()+""); |
|||
//商户名称 请根据实际情况替换 |
|||
contentMap.put("displayName", "成美国际"); |
|||
//商品名称 请根据实际情况替换 |
|||
contentMap.put("goodsName", "商品名称"); |
|||
//商品数量 请根据实际情况替换 |
|||
contentMap.put("goodsCount", 1); |
|||
//商品类别 请根据实际情况替换 |
|||
contentMap.put("goodsType", "01"); |
|||
//订单金额单位分 |
|||
contentMap.put("orderAmount", 100); |
|||
//订单币种 CNY:人民币 |
|||
contentMap.put("orderCurrencyCode", "CNY"); |
|||
//贸易类型 0004-货物贸易 |
|||
contentMap.put("tradeType", "0002"); |
|||
//支付方式 |
|||
contentMap.put("payType", "4"); |
|||
//机构代码 |
|||
contentMap.put("orgCode", "WECHATPAY"); |
|||
//payerAccount |
|||
contentMap.put("payerAccount", "omCqSs9K_z2sgvgGeIzEi5gjyL6k"); |
|||
//交易币种 CNY:人民币 |
|||
contentMap.put("payCurrencyCode", "CNY"); |
|||
//结算币种 CNY:人民币 |
|||
contentMap.put("settleCurrencyCode", "CNY"); |
|||
//分账标识 0:不分账 1:实时分账 2:延时分账 |
|||
contentMap.put("shareFlag", "0"); |
|||
//分账订单信息 shangFlag=1时必填 |
|||
JSONArray jsonArray = buildSubMerchantOrderDetails(); |
|||
contentMap.put("subMerchantOrderDetails", JSONArray.toJSONString(jsonArray)); |
|||
//回调地址 |
|||
contentMap.put("returnUrl", "http://lcoalhost:8080/gatewayTest/notify"); |
|||
//通知地址 |
|||
contentMap.put("noticeUrl", "http://lcoalhost:8080/gatewayTest/notify"); |
|||
//平台ID |
|||
contentMap.put("platformId",""); |
|||
//商户用户号 请根据实际情况选择填写 |
|||
contentMap.put("customerId", "U00001"); |
|||
//商户用户类型 请根据实际情况选择填写 |
|||
contentMap.put("customerType", "1"); |
|||
//商户用户姓名 请根据实际情况选择填写 |
|||
contentMap.put("customerName", "潘孝河"); |
|||
//商户用户证件号 请根据实际情况选择填写 |
|||
contentMap.put("customerIdNo", "460004198911216054"); |
|||
//商户用户手机号 请根据实际情况选择填写 |
|||
contentMap.put("customerTel", "13700418358"); |
|||
//商户用户银行卡号 |
|||
contentMap.put("bankCardNo", ""); |
|||
//商户用户银行卡类型 |
|||
contentMap.put("bankCardType", ""); |
|||
//有效期 |
|||
contentMap.put("expireData", ""); |
|||
//信用卡安全码 |
|||
contentMap.put("cvn", ""); |
|||
//备注 |
|||
contentMap.put("remark", ""); |
|||
//保留字段1 |
|||
contentMap.put("reserve1", ""); |
|||
//保留字段2 若要指定使用某条公众号/小程序appid时,可填写appid或对应备案号 |
|||
contentMap.put("reserve2", ""); |
|||
//预下单标识 |
|||
contentMap.put("preOrderFlag", ""); |
|||
return contentMap; |
|||
} |
|||
|
|||
/** |
|||
* 构建分账明细 |
|||
* @return |
|||
*/ |
|||
private static JSONArray buildSubMerchantOrderDetails(){ |
|||
JSONArray jsonArray = new JSONArray(); |
|||
// //子订单1 |
|||
// JSONObject subOrder1 = new JSONObject(); |
|||
// subOrder1.put("subOrderId","sub20201001"); |
|||
// subOrder1.put("subOrderAmount",50); |
|||
// //子订单1 明细1 |
|||
// JSONArray detailArray1 = new JSONArray(); |
|||
// JSONObject detail1 = new JSONObject(); |
|||
// detail1.put("shareDetailId","sub20201001sd00001"); |
|||
// detail1.put("shareParId","1000000037901581"); |
|||
// detail1.put("shareAmount",20); |
|||
// detail1.put("settleCurCode","CNY"); |
|||
// detailArray1.add(detail1); |
|||
// //子订单1 明细2 |
|||
// JSONObject detail2 = new JSONObject(); |
|||
// detail2.put("shareDetailId","sub20201001sd00002"); |
|||
// detail2.put("shareParId","1000000037901520"); |
|||
// detail2.put("shareAmount",30); |
|||
// detail2.put("settleCurCode","CNY"); |
|||
// detailArray1.add(detail2); |
|||
// subOrder1.put("shareDetail",detailArray1); |
|||
// jsonArray.add(subOrder1); |
|||
// |
|||
// //子订单2 |
|||
// JSONObject subOrder2 = new JSONObject(); |
|||
// subOrder2.put("subOrderId","sub20201002"); |
|||
// subOrder2.put("subOrderAmount",50); |
|||
// //子订单2 明细1 |
|||
// JSONArray detailArray2 = new JSONArray(); |
|||
// JSONObject detail3 = new JSONObject(); |
|||
// detail3.put("shareDetailId","sub20201002sd00001"); |
|||
// detail3.put("shareParId","1000000037901485"); |
|||
// detail3.put("shareAmount",20); |
|||
// detail3.put("settleCurCode","CNY"); |
|||
// detailArray2.add(detail3); |
|||
// //子订单2 明细2 |
|||
// JSONObject detail4 = new JSONObject(); |
|||
// detail4.put("shareDetailId","sub20201002sd00002"); |
|||
// detail4.put("shareParId","1000000037901484"); |
|||
// detail4.put("shareAmount",30); |
|||
// detail4.put("settleCurCode","CNY"); |
|||
// detailArray2.add(detail4); |
|||
// subOrder2.put("shareDetail",detailArray2); |
|||
// |
|||
// jsonArray.add(subOrder2); |
|||
|
|||
return jsonArray; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,134 @@ |
|||
package com.shop.cereshop.app.pay.xs.utils; |
|||
|
|||
import cn.hutool.core.util.StrUtil; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.alibaba.fastjson.parser.Feature; |
|||
import com.alibaba.fastjson.serializer.SerializerFeature; |
|||
import com.shop.cereshop.commons.config.XspayConfig; |
|||
import com.shop.cereshop.commons.exception.CoBusinessException; |
|||
|
|||
import java.text.SimpleDateFormat; |
|||
import java.util.Base64; |
|||
import java.util.Date; |
|||
import java.util.LinkedHashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* 商户号密钥等信息自行替换 |
|||
*/ |
|||
public class CommonUtils { |
|||
|
|||
public static final SimpleDateFormat formatDate = new SimpleDateFormat("yyyyMMdd"); |
|||
public static final SimpleDateFormat formatTime = new SimpleDateFormat("HHmmss"); |
|||
|
|||
|
|||
/** |
|||
* 构建head请求参数 |
|||
* @return |
|||
*/ |
|||
public static Map<String,String> buildHeadMap(String serCode){ |
|||
Map<String, String> headMap = new LinkedHashMap<>(); |
|||
headMap.put("serCode", serCode); |
|||
headMap.put("merCode", XspayConfig.MER_CODE); |
|||
//orderId每次请求不可重复,为请求序列号 |
|||
headMap.put("orderId", System.currentTimeMillis() + ""); |
|||
headMap.put("merDate", CommonUtils.formatDate.format(new Date())); |
|||
headMap.put("merTime", CommonUtils.formatTime.format(new Date())); |
|||
headMap.put("versionNo", "2.0"); |
|||
headMap.put("signType", XspayConfig.SIGN_TYPE); |
|||
return headMap; |
|||
} |
|||
|
|||
/** |
|||
* 构建请求参数msg |
|||
* @param headMap |
|||
* @param contentMap |
|||
* @return |
|||
*/ |
|||
public static String getRequestMsg(Map<String, String> headMap, Map<String, Object> contentMap) throws Exception { |
|||
//生成加签源串 空值不参与加签 |
|||
Map<String, Object> signMap = new LinkedHashMap<String, Object>(); |
|||
signMap.put("head", headMap); |
|||
signMap.put("content", contentMap); |
|||
//json字符串 |
|||
String jsonString = JSONObject.toJSONString(signMap); |
|||
System.out.println("【" + headMap.get("serCode") + "】接口-请求报文-JSON源串:"+jsonString); |
|||
//对json字符串md5得到加签源串 |
|||
String signData = MD5Utils.md5Hex(jsonString,XspayConfig.DEFAULT_CHARSET); |
|||
System.out.println("【" + headMap.get("serCode") + "】接口-请求报文-JSON源串MD5:"+signData); |
|||
String signMsg = null; |
|||
//加签 |
|||
if (StrUtil.equals(XspayConfig.SIGN_TYPE,"1")) {//RSA |
|||
signMsg = RSAUtils.encryptByPrivateKey(signData, XspayConfig.RSA_PRIVATE_KEY); |
|||
} else if (StrUtil.equals(XspayConfig.SIGN_TYPE,"2")) {//MD5 |
|||
signMsg = MD5Utils.md5Hex(signData + XspayConfig.RSA_PRIVATE_KEY,XspayConfig.DEFAULT_CHARSET); |
|||
} |
|||
signMap.put("sign",signMsg); |
|||
String postJsonStr = JSONObject.toJSONString(signMap); |
|||
//base64得到msg |
|||
String msg = new String(Base64.getEncoder().encode(postJsonStr.getBytes(XspayConfig.DEFAULT_CHARSET))); |
|||
return msg; |
|||
} |
|||
|
|||
/** |
|||
* 响应报文 |
|||
*/ |
|||
public static void verifySignRespMsg(String serCode, String respMsg) throws Exception { |
|||
Map<String,String> responseMap = JSONObject.parseObject(respMsg,Map.class); |
|||
String error = responseMap.get("error"); |
|||
String errorMsg = responseMap.get("errMsg"); |
|||
if(StrUtil.isNotBlank(error) && StrUtil.isNotBlank(errorMsg) && !StrUtil.equals(error, "0") && !StrUtil.equals(error, "0000") && !StrUtil.equals(error, "0001")){ |
|||
throw new CoBusinessException(error, errorMsg); |
|||
} |
|||
//得到响应msg |
|||
String resMsg = responseMap.get("msg"); |
|||
System.out.println("【" + serCode + "】接口-响应报文-msg:" + new String(Base64.getDecoder().decode(resMsg))); |
|||
if(StrUtil.isNotBlank(resMsg)){ |
|||
//解析响应msg并验签 |
|||
boolean verifyFlag = verifySignature(resMsg); |
|||
if(verifyFlag){ |
|||
System.out.println("【" + serCode + "】接口-响应报文-验签结果:"+verifyFlag); |
|||
}else { |
|||
throw new CoBusinessException("10000","支付接口验签失败"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 响应msg报文验签 |
|||
* @param msg |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
private static boolean verifySignature(String msg) throws Exception { |
|||
//base64 decode |
|||
String msgStr = new String(Base64.getDecoder().decode(msg)); |
|||
//LinkedHashMap json字符串转map时保持字段顺序不变 |
|||
LinkedHashMap<String,String> headContentMap = JSONObject.parseObject(msgStr,LinkedHashMap.class, Feature.OrderedField); |
|||
|
|||
String resSign = headContentMap.get("sign"); |
|||
headContentMap.remove("sign"); |
|||
|
|||
//headContentMap 转为json字符串时保留字段值为null的字段 |
|||
SerializerFeature[] serializerFeatures = new SerializerFeature[]{SerializerFeature.WriteMapNullValue}; |
|||
//获取json字符串 不包含sign字段 |
|||
String headContentJson = JSONObject.toJSONString(headContentMap,serializerFeatures); |
|||
System.out.println("json:" + headContentJson); |
|||
//json字符串md5得到加签源串 |
|||
String md5Hex = MD5Utils.md5Hex(headContentJson,"UTF-8"); |
|||
|
|||
//响应结果验签 |
|||
boolean verifyFlag = false; |
|||
if (StrUtil.equals(XspayConfig.SIGN_TYPE,"1")) {//RSA |
|||
if(md5Hex.equals(RSAUtils.decryptByPublicKey(resSign,XspayConfig.NEWPAY_PUBLIC_KEY))){ |
|||
verifyFlag = true; |
|||
} |
|||
} else if (StrUtil.equals(XspayConfig.SIGN_TYPE,"2")) {//MD5 |
|||
String mac = MD5Utils.md5Hex(md5Hex + XspayConfig.NEWPAY_PUBLIC_KEY,"UTF-8"); |
|||
if(mac.equals(resSign)){ |
|||
verifyFlag = true; |
|||
} |
|||
} |
|||
return verifyFlag; |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
package com.shop.cereshop.app.pay.xs.utils; |
|||
|
|||
import org.apache.http.HttpEntity; |
|||
import org.apache.http.NameValuePair; |
|||
import org.apache.http.client.entity.UrlEncodedFormEntity; |
|||
import org.apache.http.client.methods.CloseableHttpResponse; |
|||
import org.apache.http.client.methods.HttpPost; |
|||
import org.apache.http.entity.ContentType; |
|||
import org.apache.http.entity.mime.HttpMultipartMode; |
|||
import org.apache.http.entity.mime.MultipartEntityBuilder; |
|||
import org.apache.http.impl.client.CloseableHttpClient; |
|||
import org.apache.http.impl.client.HttpClients; |
|||
import org.apache.http.message.BasicNameValuePair; |
|||
import org.apache.http.util.EntityUtils; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileInputStream; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class HttpUtils { |
|||
|
|||
public static String httpPost(String url,String msg) throws Exception { |
|||
//发送请求 |
|||
CloseableHttpClient httpclient = HttpClients.createDefault(); |
|||
HttpPost httpPost = new HttpPost(url); |
|||
//此处设值防止新生将该次请求当成手机端请求 |
|||
httpPost.setHeader("User-Agent","Mozilla/5.0(Windows NT 6.1;Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0"); |
|||
List<NameValuePair> params=new ArrayList<NameValuePair>(); |
|||
params.add(new BasicNameValuePair("msg",msg)); |
|||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params,"UTF-8"); |
|||
httpPost.setEntity(entity); |
|||
CloseableHttpResponse response = httpclient.execute(httpPost); |
|||
HttpEntity responseEntity = response.getEntity(); |
|||
String responseStr = EntityUtils.toString(responseEntity, "UTF-8"); |
|||
return responseStr; |
|||
} |
|||
|
|||
|
|||
public static String httpPostFile(String url,String msg,File attachedFile) throws Exception { |
|||
//发送请求 |
|||
CloseableHttpClient httpclient = HttpClients.createDefault(); |
|||
HttpPost httpPost = new HttpPost(url); |
|||
//设置请求参数 |
|||
MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532); |
|||
builder.addTextBody("msg", msg, ContentType.TEXT_PLAIN); |
|||
//把文件加到HTTP的post请求中 |
|||
if(attachedFile != null) { |
|||
builder.addBinaryBody("attachedFile", new FileInputStream(attachedFile), ContentType.APPLICATION_OCTET_STREAM, attachedFile.getName()); |
|||
} |
|||
HttpEntity multipart = builder.build(); |
|||
|
|||
httpPost.setEntity(multipart); |
|||
CloseableHttpResponse response = httpclient.execute(httpPost); |
|||
HttpEntity responseEntity = response.getEntity(); |
|||
String responseStr = EntityUtils.toString(responseEntity, "UTF-8"); |
|||
return responseStr; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,67 @@ |
|||
package com.shop.cereshop.app.pay.xs.utils; |
|||
|
|||
import java.io.UnsupportedEncodingException; |
|||
import java.security.MessageDigest; |
|||
import java.security.NoSuchAlgorithmException; |
|||
|
|||
/** |
|||
* Static functions to simplifiy common {@link MessageDigest} tasks. This |
|||
* class is thread safe. |
|||
* |
|||
* g |
|||
* |
|||
*/ |
|||
public class MD5Utils { |
|||
|
|||
private MD5Utils() { |
|||
} |
|||
|
|||
/** |
|||
* Returns a MessageDigest for the given <code>algorithm</code>. |
|||
* |
|||
* The MessageDigest algorithm name. |
|||
* @return An MD5 digest instance. |
|||
* @throws RuntimeException |
|||
* when a {@link NoSuchAlgorithmException} is |
|||
* caught |
|||
*/ |
|||
|
|||
static MessageDigest getDigest() { |
|||
try { |
|||
return MessageDigest.getInstance("MD5"); |
|||
} catch (NoSuchAlgorithmException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Calculates the MD5 digest and returns the value as a 32 character hex |
|||
* string. |
|||
* |
|||
* @param data |
|||
* Data to digest |
|||
* @return MD5 digest as a hex string |
|||
* @throws UnsupportedEncodingException |
|||
*/ |
|||
public static String md5Hex(String data,String charset) throws UnsupportedEncodingException { |
|||
getDigest().digest(data.getBytes(charset)); |
|||
return byteArrayToHex(getDigest().digest(data.getBytes(charset))); |
|||
} |
|||
|
|||
//下面这个函数用于将字节数组换成成16进制的字符串 |
|||
public static String byteArrayToHex(byte[] byteArray) { |
|||
// 首先初始化一个字符数组,用来存放每个16进制字符 |
|||
char[] hexDigits = {'0','1','2','3','4','5','6','7','8','9', 'a','b','c','d','e','f' }; |
|||
// new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方)) |
|||
char[] resultCharArray =new char[byteArray.length * 2]; |
|||
// 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去 |
|||
int index = 0; |
|||
for (byte b : byteArray) { |
|||
resultCharArray[index++] = hexDigits[b>>> 4 & 0xf]; |
|||
resultCharArray[index++] = hexDigits[b& 0xf]; |
|||
} |
|||
// 字符数组组合成字符串返回 |
|||
return new String(resultCharArray); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,291 @@ |
|||
package com.shop.cereshop.app.pay.xs.utils; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import java.io.ByteArrayOutputStream; |
|||
import java.io.IOException; |
|||
import java.math.BigInteger; |
|||
import java.security.*; |
|||
import java.security.spec.PKCS8EncodedKeySpec; |
|||
import java.security.spec.X509EncodedKeySpec; |
|||
import java.util.Base64; |
|||
|
|||
public class RSAUtils { |
|||
|
|||
public static final String ALGORITHM = "RSA"; |
|||
|
|||
public static final String SIGN_ALGORITHMS = "SHA1WithRSA"; |
|||
|
|||
/** |
|||
* RSA最大加密明文大小 |
|||
*/ |
|||
private static final int MAX_ENCRYPT_BLOCK = 117; |
|||
|
|||
/** |
|||
* RSA最大解密密文大小 |
|||
*/ |
|||
private static final int MAX_DECRYPT_BLOCK = 128; |
|||
|
|||
|
|||
|
|||
/** |
|||
* <p> |
|||
* 公钥解密 |
|||
* </p> |
|||
* |
|||
* @param encryptedData |
|||
* 已加密数据 |
|||
* @param publicKey |
|||
* 公钥(十六进制编码) |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception { |
|||
// byte[] keyBytes = HexUtil.toByteArray(privateKey); |
|||
byte[] keyBytes = Base64.getDecoder().decode(publicKey);// 修改base64编码 |
|||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); |
|||
Key publicK = keyFactory.generatePublic(x509KeySpec); |
|||
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); |
|||
cipher.init(Cipher.DECRYPT_MODE, publicK); |
|||
int inputLen = encryptedData.length; |
|||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|||
int offSet = 0; |
|||
byte[] cache; |
|||
int i = 0; |
|||
// 对数据分段解密 |
|||
while (inputLen - offSet > 0) { |
|||
if (inputLen - offSet > MAX_DECRYPT_BLOCK) { |
|||
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); |
|||
} else { |
|||
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); |
|||
} |
|||
out.write(cache, 0, cache.length); |
|||
i++; |
|||
offSet = i * MAX_DECRYPT_BLOCK; |
|||
} |
|||
byte[] decryptedData = out.toByteArray(); |
|||
out.close(); |
|||
return decryptedData; |
|||
} |
|||
|
|||
/** |
|||
* <p> |
|||
* 公钥加密 |
|||
* </p> |
|||
* |
|||
* @param data |
|||
* 源数据 |
|||
* @param publicKey |
|||
* 公钥(十六进制编码) |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception { |
|||
// byte[] keyBytes = HexUtil.toByteArray(privateKey); |
|||
byte[] keyBytes = Base64.getDecoder().decode(publicKey);// 修改base64编码 |
|||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); |
|||
Key publicK = keyFactory.generatePublic(x509KeySpec); |
|||
// 对数据加密 |
|||
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); |
|||
cipher.init(Cipher.ENCRYPT_MODE, publicK); |
|||
int inputLen = data.length; |
|||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|||
int offSet = 0; |
|||
byte[] cache; |
|||
int i = 0; |
|||
// 对数据分段加密 |
|||
while (inputLen - offSet > 0) { |
|||
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { |
|||
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); |
|||
} else { |
|||
cache = cipher.doFinal(data, offSet, inputLen - offSet); |
|||
} |
|||
out.write(cache, 0, cache.length); |
|||
i++; |
|||
offSet = i * MAX_ENCRYPT_BLOCK; |
|||
} |
|||
byte[] encryptedData = out.toByteArray(); |
|||
out.close(); |
|||
return encryptedData; |
|||
} |
|||
|
|||
/** |
|||
* <p> |
|||
* 私钥加密 |
|||
* </p> |
|||
* |
|||
* @param data |
|||
* 源数据 |
|||
* @param privateKey |
|||
* 私钥(十六进制编码) |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception { |
|||
byte[] keyBytes = Base64.getDecoder().decode(privateKey);// 修改base64编码 |
|||
|
|||
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); |
|||
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); |
|||
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); |
|||
cipher.init(Cipher.ENCRYPT_MODE, privateK); |
|||
int inputLen = data.length; |
|||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|||
int offSet = 0; |
|||
byte[] cache; |
|||
int i = 0; |
|||
// 对数据分段加密 |
|||
while (inputLen - offSet > 0) { |
|||
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { |
|||
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); |
|||
} else { |
|||
cache = cipher.doFinal(data, offSet, inputLen - offSet); |
|||
} |
|||
out.write(cache, 0, cache.length); |
|||
i++; |
|||
offSet = i * MAX_ENCRYPT_BLOCK; |
|||
} |
|||
byte[] encryptedData = out.toByteArray(); |
|||
out.close(); |
|||
return encryptedData; |
|||
} |
|||
|
|||
/** |
|||
* <p> |
|||
* 公钥解密 |
|||
* </p> |
|||
* |
|||
* @param encryptedData |
|||
* 已加密数据 |
|||
* @param publicKey |
|||
* 公钥 |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
public static String decryptByPublicKey(String encryptedData, String publicKey) throws Exception { |
|||
return new String(decryptByPublicKey(Base64.getDecoder().decode(encryptedData), publicKey)); |
|||
} |
|||
|
|||
/** |
|||
* <p> |
|||
* 公钥加密,返回十六进制编码的密文 |
|||
* </p> |
|||
* |
|||
* @param data |
|||
* 源数据 |
|||
* @param publicKey |
|||
* 公钥(十六进制编码) |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
public static String encryptByPublicKey(String data, String publicKey) throws Exception { |
|||
return new String(Base64.getEncoder().encode(encryptByPublicKey(data.getBytes(), publicKey))); |
|||
} |
|||
|
|||
/** |
|||
* <p> |
|||
* 私钥加密,返回十六进制编码的密文 |
|||
* </p> |
|||
* |
|||
* @param data |
|||
* 源数据 |
|||
* @param privateKey |
|||
* 私钥(十六进制编码) |
|||
* @return |
|||
* @throws Exception |
|||
*/ |
|||
public static String encryptByPrivateKey(String data, String privateKey) throws Exception { |
|||
return new String(Base64.getEncoder().encode(encryptByPrivateKey(data.getBytes(), privateKey))); |
|||
} |
|||
|
|||
public static String signByPrivate(String content, PrivateKey privateKey, String input_charset) throws Exception { |
|||
if (privateKey == null) { |
|||
throw new Exception("加密私钥为空, 请设置"); |
|||
} |
|||
Signature signature = Signature.getInstance(SIGN_ALGORITHMS); |
|||
signature.initSign(privateKey); |
|||
signature.update(content.getBytes(input_charset)); |
|||
return new String(Base64.getEncoder().encode(signature.sign())); |
|||
} |
|||
|
|||
public static String signByPrivate(String content, String privateKey, String input_charset) throws Exception { |
|||
if (privateKey == null) { |
|||
throw new Exception("加密私钥为空, 请设置"); |
|||
} |
|||
PrivateKey privateKeyInfo = getPrivateKey(privateKey); |
|||
return signByPrivate(content, privateKeyInfo, input_charset); |
|||
} |
|||
|
|||
/** |
|||
* RSA验签名检查 |
|||
* |
|||
* @param content |
|||
* 待签名数据 |
|||
* @param sign |
|||
* 签名值 |
|||
* @param publicKey |
|||
* 支付宝公钥 |
|||
* @param input_charset |
|||
* 编码格式 |
|||
* @return 布尔值 |
|||
*/ |
|||
public static boolean verify(String content, String sign, String publicKey, String input_charset) { |
|||
try { |
|||
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); |
|||
byte[] encodedKey = Base64.getDecoder().decode(publicKey); |
|||
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); |
|||
return verify(content, sign, pubKey, input_charset); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return false; |
|||
|
|||
} |
|||
|
|||
public static boolean verify(String content, String sign, PublicKey publicKey, String inputCharset) { |
|||
try { |
|||
Signature signature = Signature.getInstance(SIGN_ALGORITHMS); |
|||
signature.initVerify(publicKey); |
|||
signature.update(content.getBytes(inputCharset)); |
|||
boolean bverify = signature.verify(Base64.getDecoder().decode(sign)); |
|||
return bverify; |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 得到私钥 |
|||
* |
|||
* @param key |
|||
* 密钥字符串(经过base64编码) |
|||
* @throws Exception |
|||
*/ |
|||
public static PrivateKey getPrivateKey(String key) throws Exception { |
|||
byte[] keyBytes = buildPKCS8Key(key); |
|||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); |
|||
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); |
|||
PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
|||
return privateKey; |
|||
|
|||
} |
|||
|
|||
private static byte[] buildPKCS8Key(String privateKey) throws IOException { |
|||
if (privateKey.contains("-----BEGIN PRIVATE KEY-----")) { |
|||
return Base64.getDecoder().decode(privateKey.replaceAll("-----\\w+ PRIVATE KEY-----", "")); |
|||
} else if (privateKey.contains("-----BEGIN RSA PRIVATE KEY-----")) { |
|||
final byte[] innerKey = Base64.getDecoder().decode(privateKey.replaceAll("-----\\w+ RSA PRIVATE KEY-----", "")); |
|||
final byte[] result = new byte[innerKey.length + 26]; |
|||
System.arraycopy(Base64.getDecoder().decode("MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKY="), 0, result, 0, 26); |
|||
System.arraycopy(BigInteger.valueOf(result.length - 4).toByteArray(), 0, result, 2, 2); |
|||
System.arraycopy(BigInteger.valueOf(innerKey.length).toByteArray(), 0, result, 24, 2); |
|||
System.arraycopy(innerKey, 0, result, 26, innerKey.length); |
|||
return result; |
|||
} else { |
|||
return Base64.getDecoder().decode(privateKey); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,4 @@ |
|||
package com.shop.cereshop.business.pay.xs.service; |
|||
|
|||
public class XsPayService { |
|||
} |
@ -0,0 +1,23 @@ |
|||
/* |
|||
* Copyright (C) 2017-2021 |
|||
* All rights reserved, Designed By 深圳中科鑫智科技有限公司 |
|||
* Copyright authorization contact 18814114118 |
|||
*/ |
|||
package com.shop.cereshop.commons.config; |
|||
|
|||
import com.shop.cereshop.commons.constant.IntegerEnum; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
@Component |
|||
public class PayConfig { |
|||
|
|||
// 应用appid |
|||
public static int paymentMode = IntegerEnum.ORDER_PAY_XS.getCode(); |
|||
|
|||
@Value("${pay.paymentMode:4}") |
|||
public void setPaymentMode(int paymentMode) { |
|||
this.paymentMode = paymentMode; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,104 @@ |
|||
/* |
|||
* Copyright (C) 2017-2021 |
|||
* All rights reserved, Designed By 深圳中科鑫智科技有限公司 |
|||
* Copyright authorization contact 18814114118 |
|||
*/ |
|||
package com.shop.cereshop.commons.config; |
|||
|
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
@Component |
|||
public class XspayConfig { |
|||
|
|||
// 请求网关地址 |
|||
public static String URL = "https://openapi.alipay.com/gateway.do"; |
|||
|
|||
/** |
|||
* 商户会员号 merCode |
|||
*/ |
|||
public static String MER_CODE = "10000000381"; |
|||
|
|||
/** |
|||
* 1-RSA |
|||
* 2-MD5 |
|||
*/ |
|||
public static String SIGN_TYPE = "1"; |
|||
|
|||
/** |
|||
* 1-RSA方式时:RSA_PRIVATE_KEY 对应 private.key |
|||
* 2-MD5方式时:RSA_PRIVATE_KEY 对应 10000000379_md5Key.txt |
|||
*/ |
|||
public static String RSA_PRIVATE_KEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKE9sbL2OrnqbkftH4LLPzAKYBA14MvRBUGUUQOxoAPKi3alRPn53xokZOlYKLHdEAFNkF9dIHjkJn20q5n7JRdhkeCsTQh14BZaKVNrmF5KrjdDWlGwch3Id9w+tPCkflcchSxjknVi9cRdB15mv75fEQ+4eynPPzh+8Qiz0gilAgMBAAECgYBE3mz/21vJ/O+NmSJUYytiAYx2YAzcATMVh5vyz/NgqypWStDjVG6OY+0WHamEDr+/TrnTgZtVB13JY1nIMxTr8gLAuVEhnZ4+GTaNXzJucO1hpIR2dwZy/wS276Nth3T9oZgEvVAC4q9HdzToYtcKVAnGOnWsPDLgQRYg5OQ7IQJBANHfH+TRvGOFGf4oF7X+21iyhAJrSaiDsV+eqpvdyyBwXYSwH7K/hX/c05tIZd1O8A747yP8V3pV8dc8sDD2T6cCQQDErkPZ+Z/IUKK9H66rk/kANxXvJ2nRrAujFuPAAtnpSknd3xaXnLiM9sDZMQlwpLR1OVKolGq29XQgcIE8Ws7TAkBOe541t6k3nkLGJMAZMyFb3gY30V9OQVFHbNJoT1zy2JJgWGzCL5UA59fKLhzJ0gc70iO71VXxTcqOrwdEiBfpAkAHs2MIt9NfvniAuyrVoPeQ4JdFQ9/Ky9ewzQahz/rEPZpiy4dQ7Fv1ePvYBSl/dZNzO4lW/GipPTcMxhrpSAztAkB4S07Qm8axuTyU+mSzWtSExMmXVRVIc2YL4pbSnSL/WC4r0nlDnysYOvPRatoGt5ld6IVlHiMcg4xYtqrc8RDZ"; |
|||
|
|||
/** |
|||
* 1-RSA方式时:NEWPAY_PUBLIC_KEY 对应 newPay.key |
|||
* 2-MD5方式时:NEWPAY_PUBLIC_KEY 对应 10000000379_md5Key.txt |
|||
*/ |
|||
//public static final String NEWPAY_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeI5OuApDmQr56dQzOMTH/EPWsXCeZzzq4b1/W9V3a0+kGRFLp3AiprnjbsVrzb4CUq/djLvf0mzda5YWNx74mCAQmJTXdXLF4iQhtUdTyYPC4ucoVC1QFd+K+U1xUW7avjWg3OZZzGpT7q60CPbkJqJ7XMcXxKyNYPoT4dG8qkQIDAQAB"; |
|||
public static String NEWPAY_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWtXOAopSduv3gKqHCU4QqYbLYddJkH/rovNYEIFpfjwhzzI7L8zpIrM0rocAAfcu933hP31uySQfGQsBHekPGoGyomLPHr77l5A2pPfKFHeZAlm39whCcquOw69tt4XlkS9uS+I4eKTGzwxfHUzg8nkGxMjArPdLtSUzuVfP75wIDAQAB"; |
|||
|
|||
/** |
|||
* 编码 UTF-8 |
|||
*/ |
|||
public static final String DEFAULT_CHARSET = "UTF-8"; |
|||
|
|||
|
|||
// // 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址 |
|||
// public static String REDIRECT_URL; |
|||
// |
|||
// // 退款回调地址 |
|||
// public static String REFUND_NOTIFY_URL; |
|||
// |
|||
// // 支付成功后端回调地址 |
|||
// public static String APP_NOTIFY_URL; |
|||
// |
|||
// // 平台活动保证金支付成功回调地址 |
|||
// public static String BOND_NOTIFY_URL; |
|||
|
|||
@Value("${xspay.url:}") |
|||
public void setUrl(String url) { |
|||
this.URL = url; |
|||
} |
|||
|
|||
@Value("${xspay.mer_code:}") |
|||
public void setMerCode(String merCode) { |
|||
this.MER_CODE = merCode; |
|||
} |
|||
|
|||
@Value("${xspay.sign_type:}") |
|||
public void setSignType(String signType) { |
|||
this.SIGN_TYPE = signType; |
|||
} |
|||
|
|||
@Value("${xspay.rsa_private_key:}") |
|||
public void setRsaPrivateKey(String rsaPrivateKey) { |
|||
this.RSA_PRIVATE_KEY = rsaPrivateKey; |
|||
} |
|||
|
|||
@Value("${xspay.newpay_public_key:}") |
|||
public void setNewpayPublicKey(String newpayPublicKey) { |
|||
this.NEWPAY_PUBLIC_KEY = newpayPublicKey; |
|||
} |
|||
|
|||
// @Value("${alipay.redirect_url}") |
|||
// public void setRedirectUrl(String redirectUrl) { |
|||
// this.REDIRECT_URL = redirectUrl; |
|||
// } |
|||
// |
|||
// @Value("${alipay.refund_notifyurl}") |
|||
// public void setRefundNotifyUrl(String refundNotifyUrl) { |
|||
// this.REFUND_NOTIFY_URL = refundNotifyUrl; |
|||
// } |
|||
// |
|||
// @Value("${alipay.app_notifyurl}") |
|||
// public void setAppNotifyUrl(String appNotifyUrl) { |
|||
// this.APP_NOTIFY_URL = appNotifyUrl; |
|||
// } |
|||
// |
|||
// @Value("${alipay.bond_notifyurl}") |
|||
// public void setBondNotifyUrl(String bondNotifyUrl) { |
|||
// this.BOND_NOTIFY_URL = bondNotifyUrl; |
|||
// } |
|||
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue