mirror of
https://gitee.com/egzosn/pay-java-parent.git
synced 2026-05-12 08:31:12 +08:00
支付宝证书公钥实现方式
This commit is contained in:
@@ -1,17 +1,31 @@
|
||||
package com.egzosn.pay.ali.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.egzosn.pay.ali.bean.CertEnvironment;
|
||||
import com.egzosn.pay.common.api.BasePayConfigStorage;
|
||||
import com.egzosn.pay.common.api.CertStore;
|
||||
import com.egzosn.pay.common.bean.result.PayException;
|
||||
import com.egzosn.pay.common.exception.PayErrorException;
|
||||
|
||||
/**
|
||||
* 支付配置存储
|
||||
*
|
||||
* @author egan
|
||||
* <p>
|
||||
* email egzosn@gmail.com
|
||||
* date 2016-5-18 14:09:01
|
||||
* <p>
|
||||
* email egzosn@gmail.com
|
||||
* date 2016-5-18 14:09:01
|
||||
*
|
||||
*
|
||||
* 以下证书签名相关触发前提是 {@link BasePayConfigStorage#isCertSign}等于true的情况。不然走的就是普通的方式
|
||||
*/
|
||||
public class AliPayConfigStorage extends BasePayConfigStorage {
|
||||
|
||||
/**
|
||||
* ISV代商户代用,指定appAuthToken
|
||||
*/
|
||||
private String appAuthToken;
|
||||
/**
|
||||
* 商户应用id
|
||||
*/
|
||||
@@ -27,6 +41,40 @@ public class AliPayConfigStorage extends BasePayConfigStorage {
|
||||
private String seller;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 应用公钥证书
|
||||
*/
|
||||
private Object merchantCert;
|
||||
|
||||
/**
|
||||
* 支付宝公钥证书
|
||||
*/
|
||||
private Object aliPayCert;
|
||||
/**
|
||||
* 支付宝CA证书,根证书
|
||||
*/
|
||||
private Object aliPayRootCert;
|
||||
|
||||
/**
|
||||
* 证书存储类型
|
||||
*/
|
||||
private CertStore certStoreType;
|
||||
|
||||
/**
|
||||
* 证书信息
|
||||
*/
|
||||
private CertEnvironment certEnvironment;
|
||||
|
||||
|
||||
public String getAppAuthToken() {
|
||||
return appAuthToken;
|
||||
}
|
||||
|
||||
public void setAppAuthToken(String appAuthToken) {
|
||||
this.appAuthToken = appAuthToken;
|
||||
}
|
||||
|
||||
public void setAppid(String appid) {
|
||||
this.appid = appid;
|
||||
}
|
||||
@@ -55,5 +103,63 @@ public class AliPayConfigStorage extends BasePayConfigStorage {
|
||||
this.seller = seller;
|
||||
}
|
||||
|
||||
public Object getMerchantCert() {
|
||||
return merchantCert;
|
||||
}
|
||||
|
||||
public void setMerchantCert(Object merchantCert) {
|
||||
this.merchantCert = merchantCert;
|
||||
}
|
||||
|
||||
public Object getAliPayCert() {
|
||||
return aliPayCert;
|
||||
}
|
||||
|
||||
public void setAliPayCert(Object aliPayCert) {
|
||||
this.aliPayCert = aliPayCert;
|
||||
}
|
||||
|
||||
public Object getAliPayRootCert() {
|
||||
return aliPayRootCert;
|
||||
}
|
||||
|
||||
public void setAliPayRootCert(Object aliPayRootCert) {
|
||||
this.aliPayRootCert = aliPayRootCert;
|
||||
}
|
||||
|
||||
public CertStore getCertStoreType() {
|
||||
return certStoreType;
|
||||
}
|
||||
|
||||
public void setCertStoreType(CertStore certStoreType) {
|
||||
this.certStoreType = certStoreType;
|
||||
}
|
||||
|
||||
public CertEnvironment getCertEnvironment() {
|
||||
return certEnvironment;
|
||||
}
|
||||
|
||||
public void setCertEnvironment(CertEnvironment certEnvironment) {
|
||||
this.certEnvironment = certEnvironment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化证书信息
|
||||
*/
|
||||
public void loadCertEnvironment() {
|
||||
if (isCertSign() && null != this.certEnvironment){
|
||||
return;
|
||||
}
|
||||
try (InputStream merchantCertStream = certStoreType.getInputStream(merchantCert);
|
||||
InputStream aliPayCertStream = certStoreType.getInputStream(aliPayCert);
|
||||
InputStream aliPayRootCertStream = certStoreType.getInputStream(aliPayRootCert);
|
||||
){
|
||||
this.certEnvironment = new CertEnvironment(merchantCertStream, aliPayCertStream, aliPayRootCertStream);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new PayErrorException(new PayException("读取证书异常", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
package com.egzosn.pay.ali.api;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.APP_AUTH_TOKEN;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.BIZ_CONTENT;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.CODE;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.HTTPS_REQ_URL;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.PASSBACK_PARAMS;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.PAYEE_INFO;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.PRODUCT_CODE;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.RETURN_URL;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.SIGN;
|
||||
import static com.egzosn.pay.ali.bean.AliPayConst.SUCCESS_CODE;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.egzosn.pay.ali.bean.AliPayConst;
|
||||
import com.egzosn.pay.ali.bean.AliPayMessage;
|
||||
import com.egzosn.pay.ali.bean.AliRefundResult;
|
||||
import com.egzosn.pay.ali.bean.AliTransactionType;
|
||||
import com.egzosn.pay.ali.bean.AliTransferType;
|
||||
import com.egzosn.pay.ali.bean.CertEnvironment;
|
||||
import com.egzosn.pay.ali.bean.OrderSettle;
|
||||
import com.egzosn.pay.common.api.BasePayService;
|
||||
import com.egzosn.pay.common.bean.*;
|
||||
import com.egzosn.pay.common.bean.MethodType;
|
||||
import com.egzosn.pay.common.bean.Order;
|
||||
import com.egzosn.pay.common.bean.PayMessage;
|
||||
import com.egzosn.pay.common.bean.PayOrder;
|
||||
import com.egzosn.pay.common.bean.PayOutMessage;
|
||||
import com.egzosn.pay.common.bean.RefundOrder;
|
||||
import com.egzosn.pay.common.bean.TransactionType;
|
||||
import com.egzosn.pay.common.bean.TransferOrder;
|
||||
import com.egzosn.pay.common.bean.TransferType;
|
||||
import com.egzosn.pay.common.bean.result.PayException;
|
||||
import com.egzosn.pay.common.exception.PayErrorException;
|
||||
import com.egzosn.pay.common.http.HttpConfigStorage;
|
||||
@@ -18,57 +44,17 @@ import com.egzosn.pay.common.util.Util;
|
||||
import com.egzosn.pay.common.util.sign.SignUtils;
|
||||
import com.egzosn.pay.common.util.str.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 支付宝支付服务
|
||||
*
|
||||
* @author egan
|
||||
* <p>
|
||||
* email egzosn@gmail.com
|
||||
* date 2017-2-22 20:09
|
||||
* <p>
|
||||
* email egzosn@gmail.com
|
||||
* date 2017-2-22 20:09
|
||||
*/
|
||||
public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
|
||||
/**
|
||||
* 正式测试环境
|
||||
*/
|
||||
private static final String HTTPS_REQ_URL = "https://openapi.alipay.com/gateway.do";
|
||||
/**
|
||||
* 沙箱测试环境账号
|
||||
*/
|
||||
private static final String DEV_REQ_URL = "https://openapi.alipaydev.com/gateway.do";
|
||||
|
||||
private static final String SIGN = "sign";
|
||||
|
||||
private static final String SUCCESS_CODE = "10000";
|
||||
|
||||
private static final String CODE = "code";
|
||||
/**
|
||||
* 附加参数
|
||||
*/
|
||||
private static final String PASSBACK_PARAMS = "passback_params";
|
||||
/**
|
||||
* 产品代码
|
||||
*/
|
||||
private static final String PRODUCT_CODE = "product_code";
|
||||
/**
|
||||
* 返回地址
|
||||
*/
|
||||
private static final String RETURN_URL = "return_url";
|
||||
|
||||
/**
|
||||
* 请求内容
|
||||
*/
|
||||
private static final String BIZ_CONTENT = "biz_content";
|
||||
/**
|
||||
* 应用授权概述
|
||||
*/
|
||||
private static final String APP_AUTH_TOKEN = "app_auth_token";
|
||||
/**
|
||||
* 收款方信息
|
||||
*/
|
||||
private static final String PAYEE_INFO = "payee_info";
|
||||
|
||||
/**
|
||||
* 获取对应的请求地址
|
||||
@@ -77,8 +63,9 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
*/
|
||||
@Override
|
||||
public String getReqUrl(TransactionType transactionType) {
|
||||
return payConfigStorage.isTest() ? DEV_REQ_URL : HTTPS_REQ_URL;
|
||||
return payConfigStorage.isTest() ? AliPayConst.DEV_REQ_URL : HTTPS_REQ_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对应的请求地址
|
||||
*
|
||||
@@ -89,12 +76,25 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置支付配置
|
||||
*
|
||||
* @param payConfigStorage 支付配置
|
||||
*/
|
||||
@Override
|
||||
public AliPayService setPayConfigStorage(AliPayConfigStorage payConfigStorage) {
|
||||
payConfigStorage.loadCertEnvironment();
|
||||
super.setPayConfigStorage(payConfigStorage);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AliPayService(AliPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) {
|
||||
super(payConfigStorage, configStorage);
|
||||
|
||||
}
|
||||
|
||||
public AliPayService(AliPayConfigStorage payConfigStorage) {
|
||||
super(payConfigStorage);
|
||||
this(payConfigStorage, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,18 +131,37 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
if (SIGN.equals(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
TreeMap<String, Object> response = new TreeMap((Map<String, Object> )entry.getValue());
|
||||
TreeMap<String, Object> response = new TreeMap((Map<String, Object>) entry.getValue());
|
||||
LinkedHashMap<Object, Object> linkedHashMap = new LinkedHashMap<>();
|
||||
linkedHashMap.put(CODE, response.remove(CODE));
|
||||
linkedHashMap.put("msg", response.remove("msg"));
|
||||
linkedHashMap.putAll(response);
|
||||
return SignUtils.valueOf(payConfigStorage.getSignType()).verify(JSON.toJSONString(linkedHashMap), sign, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset());
|
||||
return SignUtils.valueOf(payConfigStorage.getSignType()).verify(JSON.toJSONString(linkedHashMap), sign, getKeyPublic(params), payConfigStorage.getInputCharset());
|
||||
}
|
||||
}
|
||||
|
||||
return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset());
|
||||
return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, getKeyPublic(params), payConfigStorage.getInputCharset());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公钥信息
|
||||
* @param params 响应参数
|
||||
* @return 公钥信息
|
||||
*/
|
||||
private String getKeyPublic(Map<String, Object> params){
|
||||
if (!payConfigStorage.isCertSign()){
|
||||
return payConfigStorage.getKeyPublic();
|
||||
}
|
||||
return payConfigStorage.getCertEnvironment().getAliPayPublicKey(getAliPayCertSN(params));
|
||||
}
|
||||
/**
|
||||
* 从响应Map中提取支付宝公钥证书序列号
|
||||
*
|
||||
* @param respMap 响应Map
|
||||
* @return 支付宝公钥证书序列号
|
||||
*/
|
||||
public String getAliPayCertSN(java.util.Map<String, Object> respMap){
|
||||
return (String) respMap.get(AliPayConst.ALIPAY_CERT_SN_FIELD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验数据来源
|
||||
@@ -200,6 +219,7 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
|
||||
orderInfo.put("notify_url", payConfigStorage.getNotifyUrl());
|
||||
orderInfo.put("format", "json");
|
||||
|
||||
setAppAuthToken(orderInfo, order.getAttrs());
|
||||
|
||||
Map<String, Object> bizContent = new TreeMap<>();
|
||||
@@ -240,9 +260,11 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
if (null != order.getExpirationTime()) {
|
||||
bizContent.put(order.getTransactionType() == AliTransactionType.SWEEPPAY ? "qr_code_timeout_express" : "timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m");
|
||||
}
|
||||
|
||||
|
||||
bizContent.putAll(order.getAttrs());
|
||||
orderInfo.put(BIZ_CONTENT, JSON.toJSONString(bizContent));
|
||||
return preOrderHandler(orderInfo, order);
|
||||
return preOrderHandler(orderInfo, order);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,9 +280,22 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
orderInfo.put("charset", payConfigStorage.getInputCharset());
|
||||
orderInfo.put("timestamp", DateUtils.format(new Date()));
|
||||
orderInfo.put("version", "1.0");
|
||||
loadCertSn(orderInfo);
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载证书序列
|
||||
* @param orderInfo 订单信息
|
||||
*/
|
||||
private void loadCertSn(Map<String, Object> orderInfo){
|
||||
if (payConfigStorage.isCertSign()){
|
||||
final CertEnvironment certEnvironment = payConfigStorage.getCertEnvironment();
|
||||
setParameters(orderInfo, "app_cert_sn", certEnvironment.getMerchantCertSN());
|
||||
setParameters(orderInfo, "alipay_root_cert_sn", certEnvironment.getRootCertSN());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取输出消息,用户返回给支付端
|
||||
@@ -308,7 +343,7 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
String bizContent = (String) orderInfo.remove(BIZ_CONTENT);
|
||||
formHtml.append(getReqUrl()).append("?").append(UriVariables.getMapToParameters(orderInfo))
|
||||
.append("\" method=\"").append(method.name().toLowerCase()).append("\">");
|
||||
formHtml.append("<input type=\"hidden\" name=\"biz_content\" value=\'" ).append( bizContent ).append( "\'/>");
|
||||
formHtml.append("<input type=\"hidden\" name=\"biz_content\" value=\'").append(bizContent).append("\'/>");
|
||||
formHtml.append("</form>");
|
||||
formHtml.append("<script>document.forms['_alipaysubmit_'].submit();</script>");
|
||||
|
||||
@@ -316,7 +351,6 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取输出二维码信息,
|
||||
*
|
||||
@@ -324,7 +358,7 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
* @return 返回二维码信息,,支付时需要的
|
||||
*/
|
||||
@Override
|
||||
public String getQrPay(PayOrder order){
|
||||
public String getQrPay(PayOrder order) {
|
||||
order.setTransactionType(AliTransactionType.SWEEPPAY);
|
||||
Map<String, Object> orderInfo = orderInfo(order);
|
||||
//预订单
|
||||
@@ -345,9 +379,9 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> microPay(PayOrder order) {
|
||||
if (null == order.getTransactionType()){
|
||||
if (null == order.getTransactionType()) {
|
||||
order.setTransactionType(AliTransactionType.BAR_CODE);
|
||||
}else if (order.getTransactionType() != AliTransactionType.BAR_CODE && order.getTransactionType() != AliTransactionType.WAVE_CODE && order.getTransactionType() != AliTransactionType.SECURITY_CODE){
|
||||
} else if (order.getTransactionType() != AliTransactionType.BAR_CODE && order.getTransactionType() != AliTransactionType.WAVE_CODE && order.getTransactionType() != AliTransactionType.SECURITY_CODE) {
|
||||
throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType()));
|
||||
}
|
||||
|
||||
@@ -364,10 +398,11 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
|
||||
/**
|
||||
* 统一收单交易结算接口
|
||||
*
|
||||
* @param order 交易结算信息
|
||||
* @return 结算结果
|
||||
*/
|
||||
public Map<String, Object> settle(OrderSettle order){
|
||||
public Map<String, Object> settle(OrderSettle order) {
|
||||
//获取公共参数
|
||||
Map<String, Object> parameters = getPublicParameters(AliTransactionType.SETTLE);
|
||||
setAppAuthToken(parameters, order.getAttrs());
|
||||
@@ -422,13 +457,24 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
|
||||
/**
|
||||
* 设置支付宝授权Token
|
||||
*
|
||||
* @param parameters 参数
|
||||
* @param attrs 订单属性
|
||||
* @param attrs 订单属性
|
||||
* @return 参数
|
||||
*/
|
||||
private void setAppAuthToken(Map<String, Object> parameters, Map<String, Object> attrs) {
|
||||
setAppAuthToken(parameters);
|
||||
setParameters(parameters, APP_AUTH_TOKEN, (String) attrs.remove(APP_AUTH_TOKEN));
|
||||
}
|
||||
/**
|
||||
* 设置支付宝授权Token
|
||||
*
|
||||
* @param parameters 参数
|
||||
* @return 参数
|
||||
*/
|
||||
private void setAppAuthToken(Map<String, Object> parameters) {
|
||||
setParameters(parameters, APP_AUTH_TOKEN, payConfigStorage.getAppAuthToken());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -458,7 +504,6 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询退款
|
||||
*
|
||||
@@ -507,7 +552,6 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请
|
||||
* 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException}
|
||||
@@ -558,11 +602,11 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
bizContent.put("out_biz_no", order.getOutNo());
|
||||
bizContent.put("trans_amount", order.getAmount());
|
||||
transferType.setAttr(bizContent, order);
|
||||
setParameters(bizContent, "order_title", order);
|
||||
setParameters(bizContent, "original_order_id", order);
|
||||
setParameters(bizContent, "order_title", order);
|
||||
setParameters(bizContent, "original_order_id", order);
|
||||
setPayeeInfo(bizContent, order);
|
||||
bizContent.put("remark", order.getRemark());
|
||||
setParameters(bizContent, "business_params", order);
|
||||
setParameters(bizContent, "business_params", order);
|
||||
|
||||
//设置请求参数的集合
|
||||
parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent));
|
||||
@@ -571,24 +615,23 @@ public class AliPayService extends BasePayService<AliPayConfigStorage> {
|
||||
return getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), null, JSONObject.class);
|
||||
}
|
||||
|
||||
private Map<String, Object> setPayeeInfo(Map<String, Object> bizContent, Order order){
|
||||
private Map<String, Object> setPayeeInfo(Map<String, Object> bizContent, Order order) {
|
||||
final Object attr = order.getAttr(PAYEE_INFO);
|
||||
|
||||
if (attr instanceof String){
|
||||
if (attr instanceof String) {
|
||||
bizContent.put(PAYEE_INFO, attr);
|
||||
}
|
||||
if (attr instanceof TreeMap){
|
||||
if (attr instanceof TreeMap) {
|
||||
bizContent.put(PAYEE_INFO, attr);
|
||||
}
|
||||
if (attr instanceof Map){
|
||||
Map<String, Object> payeeInfo = new TreeMap<String, Object>((Map)attr);
|
||||
if (attr instanceof Map) {
|
||||
Map<String, Object> payeeInfo = new TreeMap<String, Object>((Map) attr);
|
||||
bizContent.put(PAYEE_INFO, payeeInfo);
|
||||
}
|
||||
return bizContent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 转账查询
|
||||
*
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.egzosn.pay.ali.bean;
|
||||
|
||||
/**
|
||||
* 常量
|
||||
* @author Egan
|
||||
* <pre>
|
||||
* email egzosn@gmail.com
|
||||
* date 2020/10/8
|
||||
* </pre>
|
||||
*/
|
||||
public final class AliPayConst {
|
||||
private AliPayConst() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 正式测试环境
|
||||
*/
|
||||
public static final String HTTPS_REQ_URL = "https://openapi.alipay.com/gateway.do";
|
||||
/**
|
||||
* 沙箱测试环境账号
|
||||
*/
|
||||
public static final String DEV_REQ_URL = "https://openapi.alipaydev.com/gateway.do";
|
||||
|
||||
public static final String SIGN = "sign";
|
||||
|
||||
public static final String SUCCESS_CODE = "10000";
|
||||
|
||||
public static final String CODE = "code";
|
||||
/**
|
||||
* 附加参数
|
||||
*/
|
||||
public static final String PASSBACK_PARAMS = "passback_params";
|
||||
/**
|
||||
* 产品代码
|
||||
*/
|
||||
public static final String PRODUCT_CODE = "product_code";
|
||||
/**
|
||||
* 返回地址
|
||||
*/
|
||||
public static final String RETURN_URL = "return_url";
|
||||
|
||||
/**
|
||||
* 请求内容
|
||||
*/
|
||||
public static final String BIZ_CONTENT = "biz_content";
|
||||
/**
|
||||
* 应用授权概述
|
||||
*/
|
||||
public static final String APP_AUTH_TOKEN = "app_auth_token";
|
||||
/**
|
||||
* 收款方信息
|
||||
*/
|
||||
public static final String PAYEE_INFO = "payee_info";
|
||||
/**
|
||||
* 收款方信息
|
||||
*/
|
||||
public static final String ALIPAY_CERT_SN_FIELD = "alipay_cert_sn";
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Alipay.com Inc. Copyright (c) 2004-2020 All Rights Reserved.
|
||||
*/
|
||||
package com.egzosn.pay.ali.bean;
|
||||
|
||||
import com.egzosn.pay.ali.utils.AntCertificationUtil;
|
||||
import com.egzosn.pay.common.bean.result.PayException;
|
||||
import com.egzosn.pay.common.exception.PayErrorException;
|
||||
import com.egzosn.pay.common.util.str.StringUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 证书模式运行时环境
|
||||
*
|
||||
* @author zhongyu
|
||||
* @version $Id: CertEnvironment.java, v 0.1 2020年01月02日 5:21 PM zhongyu Exp $
|
||||
*
|
||||
* @author egan update 2020/10/12
|
||||
*
|
||||
*/
|
||||
public class CertEnvironment {
|
||||
/**
|
||||
* 支付宝根证书内容
|
||||
*/
|
||||
private String rootCertContent;
|
||||
|
||||
/**
|
||||
* 支付宝根证书序列号
|
||||
*/
|
||||
private String rootCertSN;
|
||||
|
||||
/**
|
||||
* 商户应用公钥证书序列号
|
||||
*/
|
||||
private String merchantCertSN;
|
||||
/**
|
||||
* 默认的支付宝公钥证书序列号
|
||||
*/
|
||||
private String aliPayPublicKeySN;
|
||||
|
||||
/**
|
||||
* 缓存的不同支付宝公钥证书序列号对应的支付宝公钥
|
||||
*/
|
||||
private Map<String, String> cachedAliPayPublicKey = new ConcurrentHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* 构造证书运行环境
|
||||
*
|
||||
* @param merchantCert 商户公钥证书路径
|
||||
* @param aliPayCert 支付宝公钥证书路径
|
||||
* @param aliPayRootCert 支付宝根证书路径
|
||||
*/
|
||||
public CertEnvironment(InputStream merchantCert, InputStream aliPayCert, InputStream aliPayRootCert) {
|
||||
if (null == merchantCert || null == aliPayCert || null == aliPayRootCert) {
|
||||
throw new PayErrorException(new PayException("", "证书参数merchantCert、aliPayCert或aliPayRootCert设置不完整。"));
|
||||
}
|
||||
|
||||
this.rootCertContent = AntCertificationUtil.readFromInputStream(aliPayRootCert);
|
||||
this.rootCertSN = AntCertificationUtil.getRootCertSN(rootCertContent);
|
||||
this.merchantCertSN = AntCertificationUtil.getCertSN(AntCertificationUtil.readFromInputStream((merchantCert)));
|
||||
|
||||
String aliPayPublicCertContent = AntCertificationUtil.readFromInputStream(aliPayCert);
|
||||
aliPayPublicKeySN = AntCertificationUtil.getCertSN(aliPayPublicCertContent);
|
||||
cachedAliPayPublicKey.put(aliPayPublicKeySN,
|
||||
AntCertificationUtil.getCertPublicKey(aliPayPublicCertContent));
|
||||
}
|
||||
|
||||
public String getRootCertSN() {
|
||||
return rootCertSN;
|
||||
}
|
||||
|
||||
public String getMerchantCertSN() {
|
||||
return merchantCertSN;
|
||||
}
|
||||
|
||||
public String getAliPayPublicKey(String sn) {
|
||||
//如果没有指定sn,则默认取缓存中的第一个值
|
||||
if (StringUtils.isEmpty(sn)) {
|
||||
return cachedAliPayPublicKey.values().iterator().next();
|
||||
}
|
||||
|
||||
if (cachedAliPayPublicKey.containsKey(sn)) {
|
||||
return cachedAliPayPublicKey.get(sn);
|
||||
} else {
|
||||
//网关在支付宝公钥证书变更前,一定会确认通知到商户并在商户做出反馈后,才会更新该商户的支付宝公钥证书
|
||||
//TODO: 后续可以考虑加入自动升级支付宝公钥证书逻辑,注意并发更新冲突问题
|
||||
throw new PayErrorException(new PayException("", "支付宝公钥证书[" + sn + "]已过期,请重新下载最新支付宝公钥证书并替换原证书文件"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
package com.egzosn.pay.ali.utils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Principal;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.egzosn.pay.common.bean.result.PayException;
|
||||
import com.egzosn.pay.common.exception.PayErrorException;
|
||||
import com.egzosn.pay.common.util.IOUtils;
|
||||
import com.egzosn.pay.common.util.sign.encrypt.Base64;
|
||||
import com.egzosn.pay.common.util.str.StringUtils;
|
||||
|
||||
/**
|
||||
* 证书文件可信校验
|
||||
*
|
||||
* @author junying.wjy
|
||||
* @version $Id: AntCertificationUtil.java, v 0.1 2019-07-29 下午04:46 junying.wjy Exp $
|
||||
*
|
||||
* @author egan update 2020/10/12
|
||||
*
|
||||
*/
|
||||
public class AntCertificationUtil {
|
||||
private static final Log LOGGER = LogFactory.getLog(AntCertificationUtil.class);
|
||||
|
||||
/**
|
||||
* 验证证书是否可信
|
||||
*
|
||||
* @param certContent 需要验证的目标证书或者证书链
|
||||
* @param rootCertContent 可信根证书列表
|
||||
*/
|
||||
public static boolean isTrusted(String certContent, String rootCertContent) {
|
||||
X509Certificate[] certificates;
|
||||
try {
|
||||
certificates = readPemCertChain(certContent);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("读取证书失败", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
List<X509Certificate> rootCerts = new ArrayList<X509Certificate>();
|
||||
try {
|
||||
X509Certificate[] certs = readPemCertChain(rootCertContent);
|
||||
rootCerts.addAll(Arrays.asList(certs));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("读取根证书失败", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return verifyCertChain(certificates, rootCerts.toArray(new X509Certificate[rootCerts.size()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证证书是否是信任证书库中证书签发的
|
||||
*
|
||||
* @param cert 目标验证证书
|
||||
* @param rootCerts 可信根证书列表
|
||||
* @return 验证结果
|
||||
*/
|
||||
private static boolean verifyCert(X509Certificate cert, X509Certificate[] rootCerts) {
|
||||
try {
|
||||
cert.checkValidity();
|
||||
} catch (CertificateExpiredException e) {
|
||||
LOGGER.error("证书已经过期", e);
|
||||
return false;
|
||||
} catch (CertificateNotYetValidException e) {
|
||||
LOGGER.error("证书未激活", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
Map<Principal, X509Certificate> subjectMap = new HashMap<Principal, X509Certificate>();
|
||||
|
||||
for (X509Certificate root : rootCerts) {
|
||||
subjectMap.put(root.getSubjectDN(), root);
|
||||
}
|
||||
|
||||
Principal issuerDN = cert.getIssuerDN();
|
||||
X509Certificate issuer = subjectMap.get(issuerDN);
|
||||
if (issuer == null) {
|
||||
LOGGER.error("证书链验证失败");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
PublicKey publicKey = issuer.getPublicKey();
|
||||
verifySignature(publicKey, cert);
|
||||
} catch (PayErrorException e) {
|
||||
LOGGER.error("证书链验证失败", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证证书链是否是信任证书库中证书签发的
|
||||
*
|
||||
* @param certs 目标验证证书列表
|
||||
* @param rootCerts 可信根证书列表
|
||||
* @return 验证结果
|
||||
*/
|
||||
private static boolean verifyCertChain(X509Certificate[] certs, X509Certificate[] rootCerts) {
|
||||
boolean sorted = sortByDn(certs);
|
||||
if (!sorted) {
|
||||
LOGGER.error("证书链验证失败:不是完整的证书链");
|
||||
return false;
|
||||
}
|
||||
|
||||
//先验证第一个证书是不是信任库中证书签发的
|
||||
X509Certificate prev = certs[0];
|
||||
boolean firstOK = verifyCert(prev, rootCerts);
|
||||
if (!firstOK || certs.length == 1) {
|
||||
return firstOK;
|
||||
}
|
||||
|
||||
//验证证书链
|
||||
for (int i = 1; i < certs.length; i++) {
|
||||
X509Certificate cert = certs[i];
|
||||
if (!checkValidity(cert)){
|
||||
return false;
|
||||
}
|
||||
verifySignature(prev.getPublicKey(), cert);
|
||||
prev = cert;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证证书链是否是信任证书库中证书签发的
|
||||
*
|
||||
* @param cert 目标验证证书
|
||||
* @return 验证结果
|
||||
*/
|
||||
private static boolean checkValidity(X509Certificate cert) {
|
||||
try {
|
||||
cert.checkValidity();
|
||||
} catch (CertificateExpiredException e) {
|
||||
LOGGER.error("证书已经过期");
|
||||
return false;
|
||||
} catch (CertificateNotYetValidException e) {
|
||||
LOGGER.error("证书未激活");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void verifySignature(PublicKey publicKey, X509Certificate cert){
|
||||
try {
|
||||
cert.verify(publicKey);
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
throw new PayErrorException(new PayException("证书校验失败", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将证书链按照完整的签发顺序进行排序,排序后证书链为:[issuerA, subjectA]-[issuerA, subjectB]-[issuerB, subjectC]-[issuerC, subjectD]...
|
||||
*
|
||||
* @param certs 证书链
|
||||
* @return true:排序成功,false:证书链不完整
|
||||
*/
|
||||
private static boolean sortByDn(X509Certificate[] certs) {
|
||||
//主题和证书的映射
|
||||
Map<Principal, X509Certificate> subjectMap = new HashMap<Principal, X509Certificate>();
|
||||
//签发者和证书的映射
|
||||
Map<Principal, X509Certificate> issuerMap = new HashMap<Principal, X509Certificate>();
|
||||
//是否包含自签名证书
|
||||
boolean hasSelfSignedCert = false;
|
||||
|
||||
for (X509Certificate cert : certs) {
|
||||
if (isSelfSigned(cert)) {
|
||||
if (hasSelfSignedCert) {
|
||||
return false;
|
||||
}
|
||||
hasSelfSignedCert = true;
|
||||
}
|
||||
|
||||
Principal subjectDN = cert.getSubjectDN();
|
||||
Principal issuerDN = cert.getIssuerDN();
|
||||
|
||||
subjectMap.put(subjectDN, cert);
|
||||
issuerMap.put(issuerDN, cert);
|
||||
}
|
||||
|
||||
List<X509Certificate> certChain = new ArrayList<X509Certificate>();
|
||||
|
||||
X509Certificate current = certs[0];
|
||||
addressingUp(subjectMap, certChain, current);
|
||||
addressingDown(issuerMap, certChain, current);
|
||||
|
||||
//说明证书链不完整
|
||||
if (certs.length != certChain.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//将证书链复制到原先的数据
|
||||
for (int i = 0; i < certChain.size(); i++) {
|
||||
certs[i] = certChain.get(i);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证证书是否是自签发的
|
||||
*
|
||||
* @param cert 目标证书
|
||||
* @return true;自签发,false;不是自签发
|
||||
*/
|
||||
private static boolean isSelfSigned(X509Certificate cert) {
|
||||
return cert.getSubjectDN().equals(cert.getIssuerDN());
|
||||
}
|
||||
|
||||
/**
|
||||
* 向上构造证书链
|
||||
*
|
||||
* @param subjectMap 主题和证书的映射
|
||||
* @param certChain 证书链
|
||||
* @param current 当前需要插入证书链的证书,include
|
||||
*/
|
||||
private static void addressingUp(final Map<Principal, X509Certificate> subjectMap, List<X509Certificate> certChain,
|
||||
final X509Certificate current) {
|
||||
certChain.add(0, current);
|
||||
if (isSelfSigned(current)) {
|
||||
return;
|
||||
}
|
||||
Principal issuerDN = current.getIssuerDN();
|
||||
X509Certificate issuer = subjectMap.get(issuerDN);
|
||||
if (issuer == null) {
|
||||
return;
|
||||
}
|
||||
addressingUp(subjectMap, certChain, issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向下构造证书链
|
||||
*
|
||||
* @param issuerMap 签发者和证书的映射
|
||||
* @param certChain 证书链
|
||||
* @param current 当前需要插入证书链的证书,exclude
|
||||
*/
|
||||
private static void addressingDown(final Map<Principal, X509Certificate> issuerMap, List<X509Certificate> certChain,
|
||||
final X509Certificate current) {
|
||||
Principal subjectDN = current.getSubjectDN();
|
||||
X509Certificate subject = issuerMap.get(subjectDN);
|
||||
if (subject == null) {
|
||||
return;
|
||||
}
|
||||
if (isSelfSigned(subject)) {
|
||||
return;
|
||||
}
|
||||
certChain.add(subject);
|
||||
addressingDown(issuerMap, certChain, subject);
|
||||
}
|
||||
|
||||
private static X509Certificate[] readPemCertChain(String cert){
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(cert.getBytes());
|
||||
CertificateFactory factory = null;
|
||||
try {
|
||||
factory = CertificateFactory.getInstance("X.509");
|
||||
Collection<? extends Certificate> certificates = factory.generateCertificates(inputStream);
|
||||
return certificates.toArray(new X509Certificate[certificates.size()]);
|
||||
} catch (CertificateException e) {
|
||||
LOGGER.error("提取根证书失败", e);
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付宝根证书序列号
|
||||
*
|
||||
* @param rootCertContent 支付宝根证书内容
|
||||
* @return 支付宝根证书序列号
|
||||
*/
|
||||
public static String getRootCertSN(String rootCertContent) {
|
||||
String rootCertSN = null;
|
||||
try {
|
||||
X509Certificate[] x509Certificates = readPemCertChain(rootCertContent);
|
||||
if (null == x509Certificates){
|
||||
return null;
|
||||
}
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
for (X509Certificate c : x509Certificates) {
|
||||
if (c.getSigAlgOID().startsWith("1.2.840.113549.1.1")) {
|
||||
md.update((c.getIssuerX500Principal().getName() + c.getSerialNumber()).getBytes());
|
||||
String certSN = new BigInteger(1, md.digest()).toString(16);
|
||||
//BigInteger会把0省略掉,需补全至32位
|
||||
certSN = fillMD5(certSN);
|
||||
if (StringUtils.isEmpty(rootCertSN)) {
|
||||
rootCertSN = certSN;
|
||||
} else {
|
||||
rootCertSN = rootCertSN + "_" + certSN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOGGER.error("提取根证书失败", e);
|
||||
}
|
||||
return rootCertSN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公钥证书序列号
|
||||
*
|
||||
* @param certContent 公钥证书内容
|
||||
* @return 公钥证书序列号
|
||||
*/
|
||||
public static String getCertSN(String certContent) {
|
||||
try {
|
||||
InputStream inputStream = new ByteArrayInputStream(certContent.getBytes());
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");
|
||||
X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream);
|
||||
return md5((cert.getIssuerX500Principal().getName() + cert.getSerialNumber()).getBytes());
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new PayErrorException(new PayException(" 获取公钥证书序列号异常", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static String md5(byte[] bytes) {
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("MD5");
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new PayErrorException(new PayException("", e.getMessage()));
|
||||
}
|
||||
md.update(bytes);
|
||||
String certSN = new BigInteger(1, md.digest()).toString(16);
|
||||
//BigInteger会把0省略掉,需补全至32位
|
||||
certSN = fillMD5(certSN);
|
||||
return certSN;
|
||||
}
|
||||
|
||||
private static String fillMD5(String md5) {
|
||||
return md5.length() == 32 ? md5 : fillMD5("0" + md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取公钥证书中的公钥
|
||||
*
|
||||
* @param certContent 公钥证书内容
|
||||
* @return 公钥证书中的公钥
|
||||
*/
|
||||
public static String getCertPublicKey(String certContent) {
|
||||
try {
|
||||
InputStream inputStream = new ByteArrayInputStream(certContent.getBytes());
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");
|
||||
X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream);
|
||||
return Base64.encode(cert.getPublicKey().getEncoded());
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new PayErrorException(new PayException(" 提取公钥证书中的公钥异常", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static String readFromInputStream(InputStream cert) {
|
||||
try {
|
||||
return new String(IOUtils.toByteArray(cert), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new PayErrorException(new PayException("读取证书异常", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import com.egzosn.pay.ali.api.AliPayConfigStorage;
|
||||
import com.egzosn.pay.ali.api.AliPayService;
|
||||
import com.egzosn.pay.ali.bean.AliTransactionType;
|
||||
import com.egzosn.pay.common.api.PayService;
|
||||
import com.egzosn.pay.common.bean.CertStoreType;
|
||||
import com.egzosn.pay.common.bean.MethodType;
|
||||
import com.egzosn.pay.common.bean.PayOrder;
|
||||
|
||||
@@ -20,12 +21,39 @@ import java.util.UUID;
|
||||
*/
|
||||
public class PayTest {
|
||||
|
||||
/**
|
||||
* 设置普通公钥的方式
|
||||
* 普通公钥方式与证书公钥方式为两者取其一的方式
|
||||
* @param aliPayConfigStorage 支付宝配置信息
|
||||
*
|
||||
*/
|
||||
private static void keyPublic(AliPayConfigStorage aliPayConfigStorage){
|
||||
aliPayConfigStorage.setKeyPublic("支付宝公钥");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置证书公钥信息
|
||||
* 普通公钥方式与证书公钥方式为两者取其一的方式
|
||||
* @param aliPayConfigStorage 支付宝配置信息
|
||||
*/
|
||||
private static void certKeyPublic(AliPayConfigStorage aliPayConfigStorage){
|
||||
//设置为证书方式
|
||||
aliPayConfigStorage.setCertSign(true);
|
||||
//设置证书存储方式,这里为路径
|
||||
aliPayConfigStorage.setCertStoreType(CertStoreType.PATH);
|
||||
aliPayConfigStorage.setMerchantCert("请填写您的应用公钥证书文件路径,例如:d:/appCertPublicKey_2019051064521003.crt");
|
||||
aliPayConfigStorage.setAliPayCert("请填写您的支付宝公钥证书文件路径,例如:d:/alipayCertPublicKey_RSA2.crt");
|
||||
aliPayConfigStorage.setAliPayRootCert("请填写您的支付宝根证书文件路径,例如:d:/alipayRootCert.crt");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage();
|
||||
aliPayConfigStorage.setPid("合作者id");
|
||||
aliPayConfigStorage.setAppid("应用id");
|
||||
aliPayConfigStorage.setKeyPublic("支付宝公钥");
|
||||
//普通公钥方式与证书公钥方式为两者取其一的方式
|
||||
keyPublic(aliPayConfigStorage);
|
||||
// certKeyPublic(aliPayConfigStorage);
|
||||
aliPayConfigStorage.setKeyPrivate("应用私钥");
|
||||
aliPayConfigStorage.setNotifyUrl("异步回调地址");
|
||||
aliPayConfigStorage.setReturnUrl("同步回调地址");
|
||||
|
||||
Reference in New Issue
Block a user