Commit 5513f075 authored by 黎博's avatar 黎博

微信和支付宝与支付中心对账

parent 839d5af5
......@@ -43,7 +43,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
<version>3.6</version>
</dependency>
<!--mybatis-plus-->
......@@ -177,6 +177,23 @@
<artifactId>mongo-java-driver</artifactId>
<version>3.12.10</version>
</dependency>
<dependency>
<groupId>org.simpleframework</groupId>
<artifactId>simple-xml</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.22.30.ALL</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
</dependencies>
......
package cn.qg.holmes.entity.reconciliation;
import lombok.Data;
/**
* 支付宝支付账单相关实体类
* @author libo
* 2022-02-22
*/
@Data
public class AliPayBillData {
/**
* 交易时间
*/
private String tradeTime;
/**
* 支付宝交易号
*/
private String aliPayTradeNo;
/**
* 商户订单号
*/
private String merchantOrderNo;
/**
* 业务类型
*/
private String tradeType;
/**
* 订单金额
*/
private String orderAmount;
/**
* 商家实收
*/
private String merchantReceiveAmount;
/**
* 退款批次号/请求号,仅业务类型是退款时有值
*/
private String refundOrderNo;
}
package cn.qg.holmes.entity.reconciliation;
import lombok.Data;
/**
* 微信支付账单相关数据实体类
* @author libo
* 2022-02-21
*/
@Data
public class WxPayBillData {
/**
* 交易时间
*/
private String tradeTime;
/**
* 微信订单号
*/
private String wxOrderNo;
/**
* 商户订单号
*/
private String merchantOrderNo;
/**
* 交易类型
*/
private String tradeType;
/**
* 交易状态
*/
private String tradeStatus;
/**
* 订单金额
*/
private String orderAmount;
/**
* 微信退款单号
*/
private String wxRefundOrderNo;
/**
* 商户退款单号
*/
private String merchantRefundOrderNo;
/**
* 退款类型
*/
private String refundType;
/**
* 退款状态
*/
private String refundStatus;
/**
* 退款金额
*/
private String refundAmount;
}
package cn.qg.holmes.service.reconciliation;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.qg.holmes.entity.reconciliation.AliPayBillData;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayDataDataserviceBillDownloadurlQueryRequest;
import com.alipay.api.response.AlipayDataDataserviceBillDownloadurlQueryResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 支付宝支付账单服务
*
* @author libo
* 2022-02-22
*/
@Slf4j
@Component
public class AliPayBillService {
private final String ALI_SERVER_URL = "https://openapi.alipay.com/gateway.do";
private final String ALIPAY_PRIVATE_KEY = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3rgHa/x67gaY08RJoeVWLghAiQooLG/dOojW+639RAuirhxLyeuawbcch291uj/90MPTQy86nNKKQBbet41MQtS8L6ts/5Bjp2GYgzsQlmPCRWE7Il94/6R18fg9ZZQ4rx280BrX60hKTFcM+rn6IuNAiT7wHMcsjonRLsbWW/ZaSmMwqJcfhRU2/8Slznw2AX8jHs3X/ZZtM8usm3Hd41U9m7DIRPgejo4wlgTmUrFJrgQIWdsuSCYm88a4BzFc3Wfa2No5stqkHZizsshx52UGOgvgJdxF6PUqC9af+qQSdldTcbOeQjgQbJOLfiZoPQzyO8DaszIzJkB8FB38DAgMBAAECggEBAIbZ7qzMjtCU3+SQdLZVFlQFGjk85sI/NvL5LkJL/T4Jx65eza9OQd2XyxH1rH1GpQK2CpbceozRnOPl/rNgaRSkILU8KNmgahYM9PXzN5huz3e2AKlOrjH3wNksZ7J2+c90bRUiNCrAXji0SpLTYzyXit8V8PLLQNuZoo4MG0iNCJy8riZwUn8ZQLJnCMv1VD0yUyGSr/6LAEJjJZTrrnpibw/99hqFO8Z6UvIs9JewfvjdzamT3UxF0gURSncieQwvsq3mYlq6ppcWqzBrEqJHZJOGzZrxtsOALmOFr7KCp6qRziareK/9vb9RLnZtBSrLZqhJF/MU1oFWpHixRoECgYEA3yKt0rT2v1rjaOlMQDF7pKKfnGmRHGps9214Smsj/wOwWdlwwixWZA2DUFLOUpPQRu3dPp3ZZQdcg21NejvEbtu4CcN0lrvnVlBIrExQmm9zqjDWijljtNvf+POqMkwyLumtNtdNxWLZxdiDClosShEX9kv4ttdlF5bEc2z3V8ECgYEA0rumR+zUnvT6OS6l6msUXgZ/f/XBgcW8BLdZV6pSJJVh3I9CVgc55MlTTGTd0yNfrDPVbwloDpjVVPSNI9YNL0h9i0vfMTHolWOR7RCcqBoquvt66NjtaV70PjBm1pxNVVDaUSgCva+oOFaRnoHjOSA+jyZkKRHjwnEWlcyRZ8MCgYBnZCtE6gM3cYbUEt35FLSk+ZGZqTTLBOlO0NOfL/vy6yOozl84KdEx9Sz2aBggHUuxwf/1RrD35ixQ3bG7xLvlXjvtkjqQqaqszPCPnaDvnlrq7kxKqgLwR72FHmqrebD7Gd3f/m2T25Tq3sMBZf0FqNwAjP1Gw5GdF4gZr9EAQQKBgQCQrWgxxTUMlOAd1hru3+kxzIBIl67sq5a0HjTmbPbMSwrO5EQE0B09J8NalX198bFDhqqn+utH6kG8e9FSoyiWJ8yZj9OB8OPffGa5PUhwWNaxXOo7ZoNIbnp9H7na6aBmTIY2ZaPMGwcA9t4u1rnrhGmu2gq176RQ4FdDLRk/BQKBgQDU+Oab3jL2MoDMtGk4ZGYQUcTMB/ILtG4O1bx8BhJZ/Rula3Rt7dYPrRng3uZ0sTVh7/QRSn02hLY72UoItge3POZMEbNQLb+gzhIuU4v4hLT3H6WK2MSaLftsb9mPyW9LX76xQS4ayf5xZqeKK2jOtIZCsiLmC/ALvIytcr2DHA==";
private String format = "json";
private String charset = "GBK";
private String signType = "RSA2";
private String aliAppId = "2021002106644714";
private String merchantCertPath = "alipay/appCertPublicKey_2021002106644714.crt";
private String alipayCertPath = "alipay/alipayCertPublicKey_RSA2.crt";
private String alipayRootCertPath = "alipay/alipayRootCert.crt";
/**
* 业务账单下载路径
*/
@Value("${alipay.file.path}")
private String aliPayFilePath;
private String FILE_NAME_CONTENT = "业务明细.csv";
private String BILL_TYPE_REPAY = "交易";
private String BILL_TYPE_REFUND = "退款";
/**
* 请求支付宝对账单下载接口
*
* @param billDate 账单日期,格式:2022-02-22
* @return
* @throws IOException
* @throws AlipayApiException
*/
public List<String> downloadBillFile(String billDate) throws IOException, AlipayApiException {
log.info("开始下载支付宝对账文件,账单日期:{}", billDate);
InputStream merchantCert = new ClassPathResource(merchantCertPath).getInputStream();
InputStream alipayCert = new ClassPathResource(alipayCertPath).getInputStream();
InputStream alipayRootCert = new ClassPathResource(alipayRootCertPath).getInputStream();
AlipayConfig alipayConfig = new AlipayConfig();
alipayConfig.setServerUrl(ALI_SERVER_URL);
alipayConfig.setAppId(aliAppId);
alipayConfig.setPrivateKey(ALIPAY_PRIVATE_KEY);
alipayConfig.setAppCertContent(IOUtils.toString(merchantCert, String.valueOf(Charset.defaultCharset())));
alipayConfig.setAlipayPublicCertContent(IOUtils.toString(alipayCert, String.valueOf(Charset.defaultCharset())));
alipayConfig.setRootCertContent(IOUtils.toString(alipayRootCert, String.valueOf(Charset.defaultCharset())));
alipayConfig.setFormat(format);
alipayConfig.setCharset(charset);
alipayConfig.setSignType(signType);
DefaultAlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("bill_type", "trade");
bizContent.put("bill_date", billDate);
request.setBizContent(bizContent.toJSONString());
AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.certificateExecute(request);
return parseResponseResult(response);
}
/**
* 处理响应结果,并下载对账文件
*
* @param response
* @return
*/
public List<String> parseResponseResult(AlipayDataDataserviceBillDownloadurlQueryResponse response) {
if (!response.isSuccess()) {
log.info("获取支付宝支付账单下载地址失败.");
return null;
}
String downloadUrl = response.getBillDownloadUrl();
log.info("获取支付宝支付账单下载地址成功,地址为:{}", downloadUrl);
Map<String, String> urlParamMap = new HashMap<>();
for (String prop : StringUtils.split(downloadUrl, "&")) {
String[] splitVal = StringUtils.split(prop, "=");
urlParamMap.put(splitVal[0], splitVal[1]);
}
StringBuilder tempFilePath = new StringBuilder(aliPayFilePath);
String zipFilePath = tempFilePath.append(urlParamMap.get("downloadFileName")).toString();
downloadFileFromURL(downloadUrl, zipFilePath);
ZipUtil.unzip(zipFilePath, Charset.forName("gbk"));
String filePath = zipFilePath.substring(0, zipFilePath.lastIndexOf("."));
File file = new File(filePath);
String[] fileList = file.list();
String newFileName = StrUtil.EMPTY;
for (String fileName : fileList) {
if (fileName.contains(FILE_NAME_CONTENT)) {
newFileName = fileName;
break;
}
}
//解压后文件所在路径
String newFilePath = filePath + File.separator + newFileName;
log.info("下载到支付宝对账文件路径:{}", newFilePath);
return FileUtil.readLines(newFilePath, "gbk");
}
/**
* 解析支付宝账单文件,转换成实体类列表
*
* @param billFile
* @return
*/
public List<AliPayBillData> parseAliPayBillFile(List<String> billFile) {
log.info("开始解析支付宝账单文件.");
List<AliPayBillData> aliPayBillDataList = new ArrayList<>();
// 前五行和后四行不要
for (int i = 5; i < billFile.size() - 4; i++) {
String[] dataArray = billFile.get(i).replaceAll("\t", "").split(",");
AliPayBillData aliPayBillData = new AliPayBillData();
aliPayBillData.setAliPayTradeNo(dataArray[0]);
aliPayBillData.setMerchantOrderNo(dataArray[1]);
aliPayBillData.setTradeType(dataArray[2]);
aliPayBillData.setTradeTime(dataArray[4]);
if (StringUtils.equals(dataArray[2], BILL_TYPE_REPAY)) {
aliPayBillData.setOrderAmount(dataArray[11]);
}
if (StringUtils.equals(dataArray[2], BILL_TYPE_REFUND)) {
// 业务类型为退款时,去掉订单金额前面的负号
aliPayBillData.setOrderAmount(dataArray[11].substring(1));
// 业务类型退款时,设置退款批次号
aliPayBillData.setRefundOrderNo(dataArray[21]);
}
// 商户实收金额
aliPayBillData.setMerchantReceiveAmount(dataArray[12]);
aliPayBillDataList.add(aliPayBillData);
}
log.info("开始解析支付宝账单文件结果:{}", aliPayBillDataList);
return aliPayBillDataList;
}
@SneakyThrows
public void downloadFileFromURL(String url, String filePath) {
FileUtil.touch(filePath);
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
InputStream is = null;
HttpURLConnection conn = null;
try {
URL httpUrl = new URL(url);
conn = (HttpURLConnection) httpUrl.openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestProperty("Charset", "UTF-8");
conn.connect();
is = conn.getInputStream();
bis = new BufferedInputStream(is);
fos = new FileOutputStream(filePath);
bos = new BufferedOutputStream(fos);
byte[] buf = new byte[1024];
int length = bis.read(buf);
while (length != -1) {
bos.write(buf, 0, length);
length = bis.read(buf);
}
bos.close();
bis.close();
conn.disconnect();
} catch (Exception e) {
log.error("解析URL异常:{}", e, e.getMessage());
}
}
}
package cn.qg.holmes.service.reconciliation;
import cn.qg.holmes.entity.reconciliation.WxPayBillData;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Joiner;
import com.google.common.hash.Hashing;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import java.util.*;
import static com.google.common.base.Charsets.UTF_8;
/**
* 微信支付账单服务
*
* @author libo
* 2022-02-21
*/
@Slf4j
@Component
public class WxPayBillService {
private final String WX_API_KEY = "77f7565be7d8992ab882d4dc31271c71";
private final String WX_BILL_DOWNLOAD_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
private final String WX_FUND_FLOW_DOWNLOAD_URL = "https://api.mch.weixin.qq.com/pay/downloadfundflow";
/**
* https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6
* 下载微信支付交易账单
*
* @param billDate 下载对账单的日期,格式:20140603
* @param billType ALL(默认值),返回当日所有订单信息(不含充值退款订单)SUCCESS,返回当日成功支付的订单(不含充值退款订单), REFUND,返回当日退款订单(不含充值退款订单,RECHARGE_REFUND,返回当日充值退款订单
* @return
*/
public String downloadTradeBillFile(String billDate, String billType) {
log.info("开始下载微信支付交易账单,账单日期:{}, 账单类型:{}", billDate, billType);
String randomString = UUID.randomUUID().toString().replace("-", "");
TreeMap<String, Object> treeMap = new TreeMap<>();
treeMap.put("appid", "wx75d5a207551d0b4d");
treeMap.put("mch_id", "1604055791");
treeMap.put("nonce_str", randomString);
treeMap.put("bill_date", billDate);
treeMap.put("bill_type", billType);
treeMap.put("sign", md5Sign(treeMap));
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(WX_BILL_DOWNLOAD_URL);
CloseableHttpResponse response = null;
try {
httpPost.setHeader("Content-type", "text/xml");
String params = mapToXml(treeMap);
HttpEntity entity = new StringEntity(params);
httpPost.setEntity(entity);
response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() == 200) {
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
return content;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
/**
* 解析微信交易账单
*
* @param content 微信交易账单内容
* @param billType 账单类型
* @return
*/
public List<WxPayBillData> parseTradeBill(String content, String billType) {
log.info("开始解析微信账单文件.");
if (StringUtils.isEmpty(content)) {
return null;
}
String[] tradeBillArray = content.split("\n");
List<WxPayBillData> wxTradeBillDataList = new ArrayList<>();
// 三种类型账单下载的文件格式不一样,分别处理
if (StringUtils.equals(billType, "ALL")) {
// 第一行为表头,最后两行为统计数据,均忽略
for (int i = 1; i < tradeBillArray.length - 2; i++) {
String[] dataArray = tradeBillArray[i].replace("`", "").split(",");
WxPayBillData wxTradeBillData = new WxPayBillData();
wxTradeBillData.setTradeTime(dataArray[0]);
wxTradeBillData.setWxOrderNo(dataArray[5]);
wxTradeBillData.setMerchantOrderNo(dataArray[6]);
wxTradeBillData.setTradeType(dataArray[8]);
wxTradeBillData.setTradeStatus(dataArray[9]);
wxTradeBillData.setOrderAmount(dataArray[24]);
wxTradeBillData.setWxRefundOrderNo(dataArray[14]);
wxTradeBillData.setMerchantRefundOrderNo(dataArray[15]);
wxTradeBillData.setRefundAmount(dataArray[16]);
wxTradeBillData.setRefundType(dataArray[18]);
wxTradeBillData.setRefundStatus(dataArray[19]);
wxTradeBillDataList.add(wxTradeBillData);
}
}
if (StringUtils.equals(billType, "SUCCESS")) {
for (int i = 1; i < tradeBillArray.length - 2; i++) {
String[] dataArray = tradeBillArray[i].replace("`", "").split(",");
WxPayBillData wxTradeBillData = new WxPayBillData();
wxTradeBillData.setTradeTime(dataArray[0]);
wxTradeBillData.setWxOrderNo(dataArray[5]);
wxTradeBillData.setMerchantOrderNo(dataArray[6]);
wxTradeBillData.setTradeType(dataArray[8]);
wxTradeBillData.setTradeStatus(dataArray[9]);
wxTradeBillData.setOrderAmount(dataArray[18]);
wxTradeBillDataList.add(wxTradeBillData);
}
}
if (StringUtils.equals(billType, "REFUND")) {
for (int i = 1; i < tradeBillArray.length - 2; i++) {
String[] dataArray = tradeBillArray[i].replace("`", "").split(",");
WxPayBillData wxTradeBillData = new WxPayBillData();
wxTradeBillData.setTradeTime(dataArray[0]);
wxTradeBillData.setWxOrderNo(dataArray[5]);
wxTradeBillData.setMerchantOrderNo(dataArray[6]);
wxTradeBillData.setTradeType(dataArray[8]);
wxTradeBillData.setTradeStatus(dataArray[9]);
wxTradeBillData.setWxRefundOrderNo(dataArray[16]);
wxTradeBillData.setMerchantRefundOrderNo(dataArray[17]);
wxTradeBillData.setRefundAmount(dataArray[18]);
wxTradeBillData.setRefundType(dataArray[20]);
wxTradeBillData.setRefundStatus(dataArray[21]);
wxTradeBillDataList.add(wxTradeBillData);
}
}
return wxTradeBillDataList;
}
/**
* md5签名
*
* @param value
* @return
*/
public String md5Sign(Object value) {
JSONObject jsonMap = JSON.parseObject(JSON.toJSONString(value));
TreeMap<String, Object> treeMap = new TreeMap<>(jsonMap);
String signStr = Joiner.on("&").withKeyValueSeparator("=").join(treeMap);
return Hashing.md5().newHasher()
.putString(signStr + "&key=" + WX_API_KEY, UTF_8)
.hash()
.toString()
.toUpperCase();
}
public String mapToXml(SortedMap<String, Object> sortedMap) {
StringBuffer sb = new StringBuffer("<xml>");
Iterator iterator = sortedMap.keySet().iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
Object value = sortedMap.get(key);
sb.append("<" + key + ">");
sb.append(value);
sb.append("</" + key + ">");
}
sb.append("</xml>");
return sb.toString();
}
}
......@@ -6,7 +6,6 @@ import cn.qg.holmes.service.quality.DingRobotService;
import cn.qg.holmes.service.quality.JiraBugPoolService;
import cn.qg.holmes.service.quality.JiraService;
import cn.qg.holmes.utils.DingdingUtils;
import com.alibaba.fastjson.JSON;
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
......@@ -21,6 +20,7 @@ import java.util.List;
/**
* JIRA BUG相关定时任务
* @author libo
*/
@Component
@Slf4j
......@@ -38,8 +38,8 @@ public class BugNotifyTask {
@Value("${sjgd.ding.url}")
private String sjgdDingUrl;
@Value("${task.debug}")
private String taskDebug;
@Value("${scheduled.task.start}")
private String scheduledTaskStart;
/**
* 电商线上故障处理群钉钉机器人地址
......@@ -59,7 +59,7 @@ public class BugNotifyTask {
*/
// @Scheduled(cron = "0 0 19 * * ?")
public void SJGDJiraIssueStatisticsTimedJob() throws Exception {
if (taskDebug.equals("true")) {
if (scheduledTaskStart.equals("true")) {
log.info("开始执行数据工单BUG统计定时任务!");
Iterable<Issue> issues = jiraService.getUnsolvedIssueListByProject("SJGD");
String markdownMsg = DingdingUtils.buildMarkdownMsgForUnsolvedIssueList(issues);
......@@ -75,7 +75,7 @@ public class BugNotifyTask {
*/
// @Scheduled(cron = "0 0 10-20 ? * MON-FRI")
public void cycleJiraBugPool() throws Exception {
if (taskDebug.equals("true")) {
if (scheduledTaskStart.equals("true")) {
log.info("开始执行jira bug pool定时任务!");
QueryWrapper<JiraBugPool> jiraBugPoolQueryWrapper = new QueryWrapper<>();
jiraBugPoolQueryWrapper.eq("enable", 1);
......@@ -124,7 +124,7 @@ public class BugNotifyTask {
// @Scheduled(cron = "0 */1 * * * ?")
@Scheduled(cron = "0 0 18 ? * MON-FRI")
public void DailyCycleHandleJiraBugPool() {
if (taskDebug.equals("true")) {
if (scheduledTaskStart.equals("true")) {
// 首先遍历ding_robot, 获取状态是进行中的项目
QueryWrapper<DingRobot> dingRobotQueryWrapper = new QueryWrapper<>();
dingRobotQueryWrapper.eq("status", 1);
......@@ -177,7 +177,7 @@ public class BugNotifyTask {
// @Scheduled(cron = "0 */1 * * * ?")
@Scheduled(cron = "0 0 18 ? * *")
public void prodBugDailyNotifyTask() throws Exception {
if (taskDebug.equals("true")) {
if (scheduledTaskStart.equals("true")) {
Iterable<Issue> dsUnsolvedList = jiraService.getIssueListByJQL("project = YXMXS AND resolution = Unresolved ORDER BY priority DESC, created DESC");
Iterable<Issue> jrUnsolvedList = jiraService.getIssueListByJQL("project = YFGDZZ AND resolution = Unresolved ORDER BY priority DESC, created DESC");
DingdingUtils.sendToDingding(DingdingUtils.buildDailyProdBugNotifyMsg(dsUnsolvedList), dsProdDingUrl);
......
package cn.qg.holmes.task;
import cn.hutool.core.io.FileUtil;
import cn.qg.holmes.entity.reconciliation.AliPayBillData;
import cn.qg.holmes.entity.reconciliation.WxPayBillData;
import cn.qg.holmes.service.reconciliation.AliPayBillService;
import cn.qg.holmes.service.reconciliation.WxPayBillService;
import cn.qg.holmes.utils.DateUtils;
import cn.qg.holmes.utils.DingdingUtils;
import cn.qg.holmes.utils.JdbcUtils;
import com.alipay.api.AlipayApiException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 对账相关定时任务
* @author libo
* 2022-02-21
*/
@Slf4j
@Component
public class ReconciliationTask {
/**
* 脱敏库相关信息
*/
private final String DB_HOST = "172.30.5.27";
private final String DB_PORT = "7434";
private final String USERNAME = "haiyuan.wen";
private final String PASSWORD = "QKawjtLXHXFVdars";
@Autowired
WxPayBillService wxPayBillService;
@Autowired
AliPayBillService aliPayBillService;
/**
* 对账机器人webhook
*/
@Value("${reconciliation.ding.url}")
private String reconciliationDingUrl;
/**
* 是否启动定时任务,true-启动,false-不启动
*/
@Value("${scheduled.task.start}")
private String scheduledTaskStart;
/**
* 微信商户后台与支付中心数据对账
*/
// @Scheduled(cron = "0 */5 * * * ?")
@Scheduled(cron = "0 0 13 ? * *")
public void wxPayDailyReconciliation() {
if (scheduledTaskStart.equals("true")) {
log.info("开始微信支付对账.");
String payOrderSql = "SELECT * FROM pay_channel_order " +
"WHERE updated_at > (SELECT DATE_SUB(CURDATE(), INTERVAL 1 DAY)) AND updated_at < CURDATE() " +
"AND pay_approach IN (77, 83) " +
"AND pay_status=3";
String refundOrderSql = "SELECT * FROM refund_order " +
"WHERE updated_at > (SELECT DATE_SUB(CURDATE(), INTERVAL 1 DAY)) " +
"AND updated_at < CURDATE() " +
"AND channel_account_id='86' " +
"AND refund_status=4";
List<Map<String, Object>> payOrderList = JdbcUtils.queryForList(DB_HOST, DB_PORT, USERNAME, PASSWORD, "vpayment_center", payOrderSql);
List<Map<String, Object>> refundOrderList = JdbcUtils.queryForList(DB_HOST, DB_PORT, USERNAME, PASSWORD, "vpayment_center", refundOrderSql);
// 从微信商户后台获取数据
String billDate = DateUtils.convertDate(DateUtils.getBeforeDay(new Date(), 1), "yyyyMMdd");
String billType = "SUCCESS"; // 仅支付
List<WxPayBillData> payBillDataList = wxPayBillService.parseTradeBill(wxPayBillService.downloadTradeBillFile(billDate, billType), billType);
billType = "REFUND"; // 仅退款
List<WxPayBillData> refundBillDataList = wxPayBillService.parseTradeBill(wxPayBillService.downloadTradeBillFile(billDate, billType), billType);
// 对比交易数据,微信商户后台有,但是支付中心数据库中没有的数据列表
BigDecimal wxTradeAmount = new BigDecimal(0);
List<WxPayBillData> notInPayCenterTradeList = new ArrayList<>();
for (WxPayBillData wxTradeBillData: payBillDataList) {
String merchantOrderNo = wxTradeBillData.getMerchantOrderNo();
String orderAmount = wxTradeBillData.getOrderAmount();
wxTradeAmount = wxTradeAmount.add(new BigDecimal(orderAmount));
boolean flag = false;
for (Map<String, Object> map: payOrderList) {
if (StringUtils.equals(map.get("call_flow_no").toString(), merchantOrderNo)
&& StringUtils.equals(map.get("amount").toString(), orderAmount)) {
flag = true;
}
}
if (!flag) {
notInPayCenterTradeList.add(wxTradeBillData);
}
}
// 对比交易数据,支付中心有,但是微信商户后台没有
BigDecimal payCenterTradeAmount = new BigDecimal(0);
List<Map<String, Object>> notInWxTradeList = new ArrayList<>();
for (Map<String, Object> map: payOrderList) {
String callFlowNo = map.get("call_flow_no").toString();
String amount = map.get("amount").toString();
payCenterTradeAmount = payCenterTradeAmount.add(new BigDecimal(amount));
boolean flag = false;
for (WxPayBillData wxTradeBillData: payBillDataList) {
if (StringUtils.equals(callFlowNo, wxTradeBillData.getMerchantOrderNo())
&& StringUtils.equals(amount, wxTradeBillData.getOrderAmount())) {
flag = true;
}
}
if (!flag) {
notInWxTradeList.add(map);
}
}
// 对比退款数据,微信商户后台有,但是支付中心数据库中没有的数据列表
BigDecimal wxRefundAmount = new BigDecimal(0);
List<WxPayBillData> notInPayCenterRefundList = new ArrayList<>();
for (WxPayBillData wxTradeBillData: refundBillDataList) {
String merchantRefundOrderNo = wxTradeBillData.getMerchantRefundOrderNo();
String refundAmount = wxTradeBillData.getRefundAmount();
wxRefundAmount = wxRefundAmount.add(new BigDecimal(refundAmount));
boolean flag = false;
for (Map<String, Object> map: refundOrderList) {
if (StringUtils.equals(map.get("refund_order_id").toString(), merchantRefundOrderNo)
&& StringUtils.equals(map.get("refund_amount").toString(), refundAmount)) {
flag = true;
}
}
if (!flag) {
notInPayCenterRefundList.add(wxTradeBillData);
}
}
// 对比退款数据,支付中心有,但是微信商户后台没有
BigDecimal payCenterRefundAmount = new BigDecimal(0);
List<Map<String, Object>> notInWxRefundList = new ArrayList<>();
for (Map<String, Object> map: refundOrderList) {
String refundOrderId = map.get("refund_order_id").toString();
String refundAmount = map.get("refund_amount").toString();
payCenterRefundAmount = payCenterRefundAmount.add(new BigDecimal(refundAmount));
boolean flag = false;
for (WxPayBillData wxTradeBillData: refundBillDataList) {
if (StringUtils.equals(refundOrderId, wxTradeBillData.getMerchantRefundOrderNo())
&& StringUtils.equals(refundAmount, wxTradeBillData.getRefundAmount())) {
flag = true;
}
}
if (!flag) {
notInWxRefundList.add(map);
}
}
String text = "日期:" + billDate + "\n\n"
+ "微信商户平台-交易金额:" + wxTradeAmount + "\n\n"
+ "支付中心-微信交易金额:" + payCenterTradeAmount + "\n\n"
+ "微信商户平台-退款金额:" + wxRefundAmount + "\n\n"
+ "支付中心-微信退款金额:" + payCenterRefundAmount + "\n\n";
if (wxTradeAmount.compareTo(payCenterTradeAmount) != 0) {
int tradeErrorCount = notInPayCenterTradeList.size() + notInWxTradeList.size();
text += "**交易异常数据:共" + tradeErrorCount + "条**\n\n";
int index = 0;
if (notInPayCenterTradeList.size() > 0) {
text += "微信商户平台有,支付中心没有的数据:\n\n";
for (WxPayBillData wxTradeBillData: notInPayCenterTradeList) {
if (index > 0) {
text += "> ----------------\n\n";
}
index += 1;
text += "> 交易时间:" + wxTradeBillData.getTradeTime() + "\n\n";
text += "> 微信订单号:" + wxTradeBillData.getWxOrderNo() + "\n\n";
text += "> 商户订单号:" + wxTradeBillData.getMerchantOrderNo() + "\n\n";
text += "> 订单金额:" + wxTradeBillData.getOrderAmount() + "\n\n";
}
}
if (notInWxTradeList.size() > 0) {
text += "支付中心有,微信商户平台没有的数据:\n\n";
for (Map<String, Object> map: notInWxTradeList) {
if (index > 0) {
text += "> ----------------\n\n";
}
index += 1;
text += "> 时间:" + map.get("updated_at").toString() + "\n\n";
text += "> 微信订单号:" + map.get("third_trade_no").toString() + "\n\n";
text += "> 商户订单号:" + map.get("call_flow_no").toString() + "\n\n";
text += "> 订单金额:" + map.get("amount").toString() + "\n\n";
}
}
}
if (wxRefundAmount.compareTo(payCenterRefundAmount) != 0) {
int refundErrorCount = notInWxRefundList.size() + notInPayCenterRefundList.size();
text += "**退款异常数据:共" + refundErrorCount + "条**\n\n";
int index = 0;
if (notInPayCenterRefundList.size() > 0) {
text += "微信商户平台有,支付中心没有的数据:\n\n";
for (WxPayBillData wxPayBillData: notInPayCenterRefundList) {
if (index > 0) {
text += "> ----------------\n\n";
}
index += 1;
text += "> 时间:" + wxPayBillData.getTradeTime() + "\n\n";
text += "> 商户订单号:" + wxPayBillData.getMerchantOrderNo() + "\n\n";
text += "> 商户退款单号:" + wxPayBillData.getMerchantRefundOrderNo() + "\n\n";
text += "> 退款金额:" + wxPayBillData.getRefundAmount() + "\n\n";
}
}
if (notInWxRefundList.size() > 0) {
text += "支付中心有,微信商户平台没有的数据:\n\n";
for (Map<String, Object> map: notInWxRefundList) {
if (index > 0) {
text += "> ----------------\n\n";
}
index += 1;
text += "> 时间:" + map.get("updated_at").toString() + "\n\n";
text += "> 商户订单号:" + map.get("repay_order_id").toString() + "\n\n";
text += "> 商户退款单号:" + map.get("refund_order_id").toString() + "\n\n";
text += "> 订单金额:" + map.get("refund_amount").toString() + "\n\n";
}
}
}
String mkMsg = DingdingUtils.buildMarkdownMsg("微信支付对账" + billDate, text, false);
DingdingUtils.sendToDingding(mkMsg, reconciliationDingUrl);
}
}
/**
* 支付宝商户后台与支付中心数据对账
*/
// @Scheduled(cron = "0 */5 * * * ?")
@Scheduled(cron = "0 0 13 ? * *")
public void aliPayDailyReconciliation() throws AlipayApiException, IOException {
if (scheduledTaskStart.equals("true")) {
log.info("开始支付宝支付对账.");
String aliPayOrderSql = "SELECT * FROM pay_channel_order " +
"WHERE updated_at > (SELECT DATE_SUB(CURDATE(), INTERVAL 1 DAY)) AND updated_at < CURDATE() " +
"AND pay_approach in (81, 84) " +
"AND pay_status = 3";
String aliPayRefundSql = "SELECT * FROM refund_order " +
"WHERE updated_at > (SELECT DATE_SUB(CURDATE(), INTERVAL 1 DAY)) " +
"AND updated_at < CURDATE() " +
"AND channel_account_id = '87' " +
"AND refund_status = 4";
List<Map<String, Object>> payOrderList = JdbcUtils.queryForList(DB_HOST, DB_PORT, USERNAME, PASSWORD, "vpayment_center", aliPayOrderSql);
List<Map<String, Object>> refundOrderList = JdbcUtils.queryForList(DB_HOST, DB_PORT, USERNAME, PASSWORD, "vpayment_center", aliPayRefundSql);
String billDate = DateUtils.convertDate(DateUtils.getBeforeDay(new Date(), 1), "yyyy-MM-dd");
List<AliPayBillData> aliPayBillDataList = aliPayBillService.parseAliPayBillFile(aliPayBillService.downloadBillFile(billDate));
List<AliPayBillData> aliPayTradeList = new ArrayList<>();
List<AliPayBillData> aliPayRefundList = new ArrayList<>();;
for (AliPayBillData aliPayBillData: aliPayBillDataList) {
if (StringUtils.equals(aliPayBillData.getTradeType(), "交易")) {
aliPayTradeList.add(aliPayBillData);
}
if (StringUtils.equals(aliPayBillData.getTradeType(), "退款")) {
aliPayRefundList.add(aliPayBillData);
}
}
// 对比交易数据,支付宝商户后台有,但是支付中心数据库中没有的数据列表
BigDecimal aliTradeAmount = new BigDecimal(0);
List<AliPayBillData> notInPayCenterTradeList = new ArrayList<>();
for (AliPayBillData aliPayBillData: aliPayTradeList) {
String merchantOrderNo = aliPayBillData.getMerchantOrderNo();
String orderAmount = aliPayBillData.getOrderAmount();
aliTradeAmount = aliTradeAmount.add(new BigDecimal(orderAmount));
boolean flag = false;
for (Map<String, Object> map: payOrderList) {
if (StringUtils.equals(map.get("call_flow_no").toString(), merchantOrderNo)
&& StringUtils.equals(map.get("amount").toString(), orderAmount)) {
flag = true;
}
}
if (!flag) {
notInPayCenterTradeList.add(aliPayBillData);
}
}
// 对比交易数据,支付中心有,但是支付宝商户后台没有
BigDecimal payCenterTradeAmount = new BigDecimal(0);
List<Map<String, Object>> notInAliTradeList = new ArrayList<>();
for (Map<String, Object> map: payOrderList) {
String callFlowNo = map.get("call_flow_no").toString();
String amount = map.get("amount").toString();
payCenterTradeAmount = payCenterTradeAmount.add(new BigDecimal(amount));
boolean flag = false;
for (AliPayBillData aliPayBillData: aliPayTradeList) {
if (StringUtils.equals(callFlowNo, aliPayBillData.getMerchantOrderNo())
&& StringUtils.equals(amount, aliPayBillData.getOrderAmount())) {
flag = true;
}
}
if (!flag) {
notInAliTradeList.add(map);
}
}
// 对比退款数据,支付宝商户后台有,但是支付中心数据库中没有的数据列表
BigDecimal aliRefundAmount = new BigDecimal(0);
List<AliPayBillData> notInPayCenterRefundList = new ArrayList<>();
for (AliPayBillData aliPayBillData: aliPayRefundList) {
String merchantRefundOrderNo = aliPayBillData.getRefundOrderNo();
String refundAmount = aliPayBillData.getOrderAmount();
aliRefundAmount = aliRefundAmount.add(new BigDecimal(refundAmount));
boolean flag = false;
for (Map<String, Object> map: refundOrderList) {
if (StringUtils.equals(map.get("refund_order_id").toString(), merchantRefundOrderNo)
&& StringUtils.equals(map.get("refund_amount").toString(), refundAmount)) {
flag = true;
}
}
if (!flag) {
notInPayCenterRefundList.add(aliPayBillData);
}
}
// 对比退款数据,支付中心有,但是支付宝商户后台没有
BigDecimal payCenterRefundAmount = new BigDecimal(0);
List<Map<String, Object>> notInAliRefundList = new ArrayList<>();
for (Map<String, Object> map: refundOrderList) {
String refundOrderId = map.get("refund_order_id").toString();
String refundAmount = map.get("refund_amount").toString();
payCenterRefundAmount = payCenterRefundAmount.add(new BigDecimal(refundAmount));
boolean flag = false;
for (AliPayBillData aliPayBillData: aliPayRefundList) {
if (StringUtils.equals(refundOrderId, aliPayBillData.getRefundOrderNo())
&& StringUtils.equals(refundAmount, aliPayBillData.getOrderAmount())) {
flag = true;
}
}
if (!flag) {
notInAliRefundList.add(map);
}
}
String text = "日期:" + billDate + "\n\n"
+ "支付宝商户平台-交易金额:" + aliTradeAmount + "\n\n"
+ "支付中心-支付宝交易金额:" + payCenterTradeAmount + "\n\n"
+ "支付宝商户平台-退款金额:" + aliRefundAmount + "\n\n"
+ "支付中心-支付宝退款金额:" + payCenterRefundAmount + "\n\n";
if (aliTradeAmount.compareTo(payCenterTradeAmount) != 0) {
int tradeErrorCount = notInPayCenterTradeList.size() + notInAliTradeList.size();
text += "**交易异常数据:共" + tradeErrorCount + "条**\n\n";
int index = 0;
if (notInPayCenterTradeList.size() > 0) {
text += "支付宝平台有,支付中心没有的数据:\n\n";
for (AliPayBillData aliPayBillData: notInPayCenterTradeList) {
if (index > 0) {
text += "> ----------------\n\n";
}
index += 1;
text += "> 交易时间:" + aliPayBillData.getTradeTime() + "\n\n";
text += "> 支付宝订单号:" + aliPayBillData.getAliPayTradeNo() + "\n\n";
text += "> 商户订单号:" + aliPayBillData.getMerchantOrderNo() + "\n\n";
text += "> 订单金额:" + aliPayBillData.getOrderAmount() + "\n\n";
}
}
if (notInAliTradeList.size() > 0) {
text += "支付中心有,支付宝平台没有的数据:\n\n";
for (Map<String, Object> map: notInAliTradeList) {
if (index > 0) {
text += "> ----------------\n\n";
}
index += 1;
text += "> 时间:" + map.get("updated_at").toString() + "\n\n";
text += "> 支付宝订单号:" + map.get("third_trade_no").toString() + "\n\n";
text += "> 商户订单号:" + map.get("call_flow_no").toString() + "\n\n";
text += "> 订单金额:" + map.get("amount").toString() + "\n\n";
}
}
}
if (aliRefundAmount.compareTo(payCenterRefundAmount) != 0) {
int refundErrorCount = notInAliRefundList.size() + notInPayCenterRefundList.size();
text += "**退款异常数据:共" + refundErrorCount + "条**\n\n";
int index = 0;
if (notInPayCenterRefundList.size() > 0) {
text += "支付宝平台有,支付中心没有的数据:\n\n";
for (AliPayBillData aliPayBillData: notInPayCenterRefundList) {
if (index > 0) {
text += "> ----------------\n\n";
}
index += 1;
text += "> 时间:" + aliPayBillData.getTradeTime() + "\n\n";
text += "> 商户订单号:" + aliPayBillData.getMerchantOrderNo() + "\n\n";
text += "> 商户退款单号:" + aliPayBillData.getRefundOrderNo() + "\n\n";
text += "> 退款金额:" + aliPayBillData.getOrderAmount() + "\n\n";
}
}
if (notInAliRefundList.size() > 0) {
text += "支付中心有,支付宝平台没有的数据:\n\n";
for (Map<String, Object> map: notInAliRefundList) {
if (index > 0) {
text += "> ----------------\n\n";
}
index += 1;
text += "> 时间:" + map.get("updated_at").toString() + "\n\n";
text += "> 商户订单号:" + map.get("repay_order_id").toString() + "\n\n";
text += "> 商户退款单号:" + map.get("refund_order_id").toString() + "\n\n";
text += "> 订单金额:" + map.get("refund_amount").toString() + "\n\n";
}
}
}
String mkMsg = DingdingUtils.buildMarkdownMsg("支付宝对账" + billDate, text, false);
DingdingUtils.sendToDingding(mkMsg, reconciliationDingUrl);
}
}
}
package cn.qg.holmes.utils;
import org.apache.commons.lang3.time.FastDateFormat;
import java.text.ParseException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
/**
* 日期格式化工具类
*/
public class DateUtils {
public static Date getCurrentTime() {
return new Date(System.currentTimeMillis());
}
public static String getCurrentDate(String pattern) {
LocalDate now = LocalDate.now();
return now.format(DateTimeFormatter.ofPattern(pattern));
}
public static String convertDate(Date date, String pattern) {
return FastDateFormat
.getInstance(pattern)
.format(date);
}
public static Date convertStr(String date, String pattern) throws ParseException {
return FastDateFormat
.getInstance(pattern)
.parse(date);
}
public static long betweenDays(Date dateBefore, Date dateAfter) {
return Duration.between(toLocalDate(dateBefore).atStartOfDay(), toLocalDate(dateAfter).atStartOfDay())
.toDays();
}
public static LocalDate toLocalDate(Date date) {
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
}
public static String formatLocalTime(LocalTime localTime, String pattern) {
return DateTimeFormatter.ofPattern(pattern).format(localTime);
}
public static String formatLocalDate(LocalDate localDate, String pattern) {
return DateTimeFormatter.ofPattern(pattern).format(localDate);
}
// 获得某天最小时间 2020-02-17 00:00:00
public static Date getStartOfDay(Date date) {
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
}
// 获得某天最大时间 2020-02-19 23:59:59
public static Date getEndOfDay(Date date) {
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
}
// 获取指定日期的前几天
public static Date getBeforeDay(Date date, int day) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, day * -1);
return calendar.getTime();
}
// 获取指定日期的后几天
public static Date getAfterDay(Date date, int day) {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(date.getTime());
c.add(Calendar.DATE, day);
return new Date(c.getTimeInMillis());
}
}
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQICARFv38DKhJ31odYtzGpDANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0
aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs
YXNzIDIgUjEwHhcNMjAxMTE2MDcwMTMwWhcNMjIxMTE2MDcwMTMwWjCBjzELMAkGA1UEBhMCQ04x
KjAoBgNVBAoMIeWMl+S6rOmHj+WMlua0vuenkeaKgOaciemZkOWFrOWPuDEPMA0GA1UECwwGQWxp
cGF5MUMwQQYDVQQDDDrmlK/ku5jlrp0o5Lit5Zu9Kee9kee7nOaKgOacr+aciemZkOWFrOWPuC0y
MDg4OTMxOTU1NzA1MzIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm9LT+VzzI6Zy
r7ePg67m+6AB593Mpz9Pu3mdaYqhyN1MsW5p9l7Fjj/PSKJrCW7FzclCdDMVJrIMdopUMrscaHY8
ddncLA3+E0r5XK5+2lBmb35SNNlsKfVgnt122AymK5mkdar7rqOtQ1QrXM8NtlcY3UdZTUaPWbz5
0A/t2tlMoUSM9EANgqQbrT7wPIG+5bq6VWW2piVcyx+Qh0CLHLd9E3QSJKyYzYfJpLFjiQlSa8/S
aEHuBQTREyuO0/Kz/2Pxt5r7kkU80Yd0l4E1/oYPd3EhMbRpFNh79LxKjVtgxWq1hf2sdarvT9Ce
OftxnDn3yXTFXa9CEQqtByoANQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCA/gwDQYJKoZIhvcNAQEL
BQADggEBAEiC3Dzm+oayzvSJoZZA3jrxdRJhJ6pQQ1vGcCe1mNfR7XKA0r4ht4J4weTryF9tmUVw
6D+5UFdiA/2AfwOQ3rxmhc3TTN9xFDzm5koGfHFI9UV46PfBtugTUbgc+5HqsPPzcy6SvsQk3V17
/3ua+c/wM4Db9S7JX0Ug2kfFYsGZ/IxRQYmTiHDcD4FYUKjAep0uPbVu0/Nc5gR2OKiGwwr8MvDQ
dmozWdgJ42on5N7KkRNy0rRINQ9Z/QNMtQQ8GJB73rEbdty7q4psBsZ5Si2FuejNJSN4BuLybcM8
IZRSYtUFCmzX9QoKfUMQO215f6bxMK9GZFu0TyWTxlXjvr0=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG
EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw
MzExNTlaFw00MjA3MDcwMzExNTlaMC4xCzAJBgNVBAYTAkNOMQ4wDAYDVQQKDAVO
UkNBQzEPMA0GA1UEAwwGUk9PVENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE
MPCca6pmgcchsTf2UnBeL9rtp4nw+itk1Kzrmbnqo05lUwkwlWK+4OIrtFdAqnRT
V7Q9v1htkv42TsIutzd126NdMFswHwYDVR0jBBgwFoAUTDKxl9kzG8SmBcHG5Yti
W/CXdlgwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFEwysZfZ
MxvEpgXBxuWLYlvwl3ZYMAwGCCqBHM9VAYN1BQADSAAwRQIgG1bSLeOXp3oB8H7b
53W+CKOPl2PknmWEq/lMhtn25HkCIQDaHDgWxWFtnCrBjH16/W3Ezn7/U/Vjo5xI
pDoiVhsLwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0zCCA7ugAwIBAgIIH8+hjWpIDREwDQYJKoZIhvcNAQELBQAwejELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMTEzNDg0MFoXDTM4MDIyODEzNDg0
MFowejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNV
BAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAtytTRcBNuur5h8xuxnlKJetT65cHGemGi8oD+beHFPTk
rUTlFt9Xn7fAVGo6QSsPb9uGLpUFGEdGmbsQ2q9cV4P89qkH04VzIPwT7AywJdt2
xAvMs+MgHFJzOYfL1QkdOOVO7NwKxH8IvlQgFabWomWk2Ei9WfUyxFjVO1LVh0Bp
dRBeWLMkdudx0tl3+21t1apnReFNQ5nfX29xeSxIhesaMHDZFViO/DXDNW2BcTs6
vSWKyJ4YIIIzStumD8K1xMsoaZBMDxg4itjWFaKRgNuPiIn4kjDY3kC66Sl/6yTl
YUz8AybbEsICZzssdZh7jcNb1VRfk79lgAprm/Ktl+mgrU1gaMGP1OE25JCbqli1
Pbw/BpPynyP9+XulE+2mxFwTYhKAwpDIDKuYsFUXuo8t261pCovI1CXFzAQM2w7H
DtA2nOXSW6q0jGDJ5+WauH+K8ZSvA6x4sFo4u0KNCx0ROTBpLif6GTngqo3sj+98
SZiMNLFMQoQkjkdN5Q5g9N6CFZPVZ6QpO0JcIc7S1le/g9z5iBKnifrKxy0TQjtG
PsDwc8ubPnRm/F82RReCoyNyx63indpgFfhN7+KxUIQ9cOwwTvemmor0A+ZQamRe
9LMuiEfEaWUDK+6O0Gl8lO571uI5onYdN1VIgOmwFbe+D8TcuzVjIZ/zvHrAGUcC
AwEAAaNdMFswCwYDVR0PBAQDAgEGMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFF90
tATATwda6uWx2yKjh0GynOEBMB8GA1UdIwQYMBaAFF90tATATwda6uWx2yKjh0Gy
nOEBMA0GCSqGSIb3DQEBCwUAA4ICAQCVYaOtqOLIpsrEikE5lb+UARNSFJg6tpkf
tJ2U8QF/DejemEHx5IClQu6ajxjtu0Aie4/3UnIXop8nH/Q57l+Wyt9T7N2WPiNq
JSlYKYbJpPF8LXbuKYG3BTFTdOVFIeRe2NUyYh/xs6bXGr4WKTXb3qBmzR02FSy3
IODQw5Q6zpXj8prYqFHYsOvGCEc1CwJaSaYwRhTkFedJUxiyhyB5GQwoFfExCVHW
05ZFCAVYFldCJvUzfzrWubN6wX0DD2dwultgmldOn/W/n8at52mpPNvIdbZb2F41
T0YZeoWnCJrYXjq/32oc1cmifIHqySnyMnavi75DxPCdZsCOpSAT4j4lAQRGsfgI
kkLPGQieMfNNkMCKh7qjwdXAVtdqhf0RVtFILH3OyEodlk1HYXqX5iE5wlaKzDop
PKwf2Q3BErq1xChYGGVS+dEvyXc/2nIBlt7uLWKp4XFjqekKbaGaLJdjYP5b2s7N
1dM0MXQ/f8XoXKBkJNzEiM3hfsU6DOREgMc1DIsFKxfuMwX3EkVQM1If8ghb6x5Y
jXayv+NLbidOSzk4vl5QwngO/JYFMkoc6i9LNwEaEtR9PhnrdubxmrtM+RjfBm02
77q3dSWFESFQ4QxYWew4pHE0DpWbWy/iMIKQ6UZ5RLvB8GEcgt8ON7BBJeMc+Dyi
kT9qhqn+lw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAgygAwIBAgIIQX76UsB/30owDAYIKoZIzj0EAwMFADB6MQswCQYDVQQG
EwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UECwwXQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNpYWwgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgRTEwHhcNMTkwNDI4MTYyMDQ0WhcNNDkwNDIwMTYyMDQ0
WjB6MQswCQYDVQQGEwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UE
CwwXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNp
YWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASCCRa94QI0vR5Up9Yr9HEupz6hSoyjySYqo7v837KnmjveUIUNiuC9pWAU
WP3jwLX3HkzeiNdeg22a0IZPoSUCpasufiLAnfXh6NInLiWBrjLJXDSGaY7vaokt
rpZvAdmjXTBbMAsGA1UdDwQEAwIBBjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRZ
4ZTgDpksHL2qcpkFkxD2zVd16TAfBgNVHSMEGDAWgBRZ4ZTgDpksHL2qcpkFkxD2
zVd16TAMBggqhkjOPQQDAwUAA2gAMGUCMQD4IoqT2hTUn0jt7oXLdMJ8q4vLp6sg
wHfPiOr9gxreb+e6Oidwd2LDnC4OUqCWiF8CMAzwKs4SnDJYcMLf2vpkbuVE4dTH
Rglz+HGcTLWsFs4KxLsq7MuU+vJTBUeDJeDjdA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIUEMdk6dVgOEIS2cCP0Q43P90Ps5YwDQYJKoZIhvcNAQEF
BQAwajELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM
E0NoaW5hIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMMH2lUcnVzQ2hpbmEgQ2xhc3Mg
MiBSb290IENBIC0gRzMwHhcNMTMwNDE4MDkzNjU2WhcNMzMwNDE4MDkzNjU2WjBq
MQswCQYDVQQGEwJDTjETMBEGA1UECgwKaVRydXNDaGluYTEcMBoGA1UECwwTQ2hp
bmEgVHJ1c3QgTmV0d29yazEoMCYGA1UEAwwfaVRydXNDaGluYSBDbGFzcyAyIFJv
b3QgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPPShpV
nJbMqqCw6Bz1kehnoPst9pkr0V9idOwU2oyS47/HjJXk9Rd5a9xfwkPO88trUpz5
4GmmwspDXjVFu9L0eFaRuH3KMha1Ak01citbF7cQLJlS7XI+tpkTGHEY5pt3EsQg
wykfZl/A1jrnSkspMS997r2Gim54cwz+mTMgDRhZsKK/lbOeBPpWtcFizjXYCqhw
WktvQfZBYi6o4sHCshnOswi4yV1p+LuFcQ2ciYdWvULh1eZhLxHbGXyznYHi0dGN
z+I9H8aXxqAQfHVhbdHNzi77hCxFjOy+hHrGsyzjrd2swVQ2iUWP8BfEQqGLqM1g
KgWKYfcTGdbPB1MCAwEAAaNjMGEwHQYDVR0OBBYEFG/oAMxTVe7y0+408CTAK8hA
uTyRMB8GA1UdIwQYMBaAFG/oAMxTVe7y0+408CTAK8hAuTyRMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBLnUTfW7hp
emMbuUGCk7RBswzOT83bDM6824EkUnf+X0iKS95SUNGeeSWK2o/3ALJo5hi7GZr3
U8eLaWAcYizfO99UXMRBPw5PRR+gXGEronGUugLpxsjuynoLQu8GQAeysSXKbN1I
UugDo9u8igJORYA+5ms0s5sCUySqbQ2R5z/GoceyI9LdxIVa1RjVX8pYOj8JFwtn
DJN3ftSFvNMYwRuILKuqUYSHc2GPYiHVflDh5nDymCMOQFcFG3WsEuB+EYQPFgIU
1DHmdZcz7Llx8UOZXX2JupWCYzK1XhJb+r4hK5ncf/w8qGtYlmyJpxk3hr1TfUJX
Yf4Zr0fJsGuv
-----END CERTIFICATE-----
\ No newline at end of file
-----BEGIN CERTIFICATE-----
MIIErjCCA5agAwIBAgIQICARGDrIh1PZVJ5tAyihwzANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0
aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs
YXNzIDEgUjEwHhcNMjAxMTE4MDkwOTE2WhcNMjIxMTE4MDkwOTE2WjB2MQswCQYDVQQGEwJDTjEq
MCgGA1UECgwh5YyX5Lqs6YeP5YyW5rS+56eR5oqA5pyJ6ZmQ5YWs5Y+4MQ8wDQYDVQQLDAZBbGlw
YXkxKjAoBgNVBAMMITIwODg5MzE5NTU3MDUzMjMtMjAyMTAwMjEwNjY0NDcxNDCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBALeuAdr/HruBpjTxEmh5VYuCECJCigsb906iNb7rf1EC6KuH
EvJ65rBtxyHb3W6P/3Qw9NDLzqc0opAFt63jUxC1Lwvq2z/kGOnYZiDOxCWY8JFYTsiX3j/pHXx+
D1llDivHbzQGtfrSEpMVwz6ufoi40CJPvAcxyyOidEuxtZb9lpKYzColx+FFTb/xKXOfDYBfyMez
df9lm0zy6ybcd3jVT2bsMhE+B6OjjCWBOZSsUmuBAhZ2y5IJibzxrgHMVzdZ9rY2jmy2qQdmLOyy
HHnZQY6C+Al3EXo9SoL1p/6pBJ2V1Nxs55COBBsk4t+Jmg9DPI7wNqzMjMmQHwUHfwMCAwEAAaOC
ASkwggElMB8GA1UdIwQYMBaAFHEH4gRhFuTl8mXrMQ/J4PQ8mtWRMB0GA1UdDgQWBBQImTjGQ3Bx
PQVszP7T4OZ+am6YGTBABgNVHSAEOTA3MDUGB2CBHAFuAQEwKjAoBggrBgEFBQcCARYcaHR0cDov
L2NhLmFsaXBheS5jb20vY3BzLnBkZjAOBgNVHQ8BAf8EBAMCBsAwLwYDVR0fBCgwJjAkoCKgIIYe
aHR0cDovL2NhLmFsaXBheS5jb20vY3JsNDcuY3JsMGAGCCsGAQUFBwEBBFQwUjAoBggrBgEFBQcw
AoYcaHR0cDovL2NhLmFsaXBheS5jb20vY2E2LmNlcjAmBggrBgEFBQcwAYYaaHR0cDovL2NhLmFs
aXBheS5jb206ODM0MC8wDQYJKoZIhvcNAQELBQADggEBAAuKP7KQadec991A8SFAZsaIHIPFb5mb
Tb+shnzi71IeNsAnBWqhjKzBwEMAeyJt6z652pOE3zuzJ5XimPS5AUz8ZK91D9ViEuk03vBs1r2g
ZW5dEmqZwg0m2q7KXV8hNjzSKqlSyVojxjHwvdh6iHsKCtiMz0CRic6CkKMSTInP98g1lYjxsbL2
Vr1o22BvOtdr2sGeYFZbe8eNURyfmeiIPGcOwOwLURc0U32tm4MP+PqceW6jzRjjh7VCU0luOHWT
ZRnO4jWrhkF3E1Cqgg2OUWEdd2yAsbUuMbVb7Hm1HPNGOY5/V/D1AW3l4Obc+SffrPZKM/QJK3sq
VUPXWU8=
-----END CERTIFICATE-----
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment