Commit 3765b47f authored by shangtx's avatar shangtx

feat: 支付代码搬运、管理端推广码

parent 23ce2663
......@@ -19,6 +19,11 @@
<artifactId>common</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
......
package com.onsiteservice.admin.service.weixin;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaCodeLineColor;
import com.onsiteservice.common.service.dto.MiniQrCodeDTO;
import com.onsiteservice.core.exception.ServiceException;
import com.onsiteservice.core.result.Result;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import static com.onsiteservice.core.result.ResultGenerator.success;
@Transactional(rollbackFor = Exception.class)
@Service
public class AdminWeixinService {
@Resource
private WxMaService wxMaService;
/**
* 获取推广二维码
*/
public Result getQrCode(MiniQrCodeDTO miniQrCodeDTO) {
try {
// 获取base64数据
byte[] qrCode = wxMaService.getQrcodeService().createWxaCodeUnlimitBytes(miniQrCodeDTO.getScene(),
miniQrCodeDTO.getPath(), miniQrCodeDTO.getWidth(), false, new WxMaCodeLineColor("0", "0", "0"), false);
return success(qrCode, "获取微信小程序码成功");
} catch (Exception e) {
throw new ServiceException("获取微信小程序码失败");
}
}
}
package com.onsiteservice.miniapp.service.weixin.dto;
package com.onsiteservice.common.service.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
......
......@@ -7,4 +7,13 @@ public class SysParamConstants {
/* 默认头像 */
public static final String AVATAR_URL = "";
/**
* 微信支付回调地址
*/
public final static String MINI_WECHAT_PAY_NOTIFY_URL = "MINI_WECHAT_PAY_NOTIFY_URL";
/**
* 微信退款回调地址
*/
public final static String MINI_WECHAT_REFUND_NOTIFY_URL = "MINI_WECHAT_REFUND_NOTIFY_URL";
}
......@@ -29,7 +29,6 @@ wx:
pay:
appId: wxa56f0845ca456bac #微信公众号或者小程序等的appid
mchId: 1602411974 #微信支付商户号
wechat_key: xxxxxxx
# 公众号
###
......
package com.onsiteservice.dao.mapper.pay;
import com.onsiteservice.dao.common.Mapper;
import com.onsiteservice.entity.pay.PayChannelWechat;
public interface PayChannelWechatMapper extends Mapper<PayChannelWechat> {
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.onsiteservice.dao.mapper.pay.PayChannelWechatMapper">
<resultMap id="BaseResultMap" type="com.onsiteservice.entity.pay.PayChannelWechat">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="merchant_name" jdbcType="VARCHAR" property="merchantName" />
<result column="appid" jdbcType="VARCHAR" property="appid" />
<result column="mchid" jdbcType="VARCHAR" property="mchid" />
<result column="remark" jdbcType="VARCHAR" property="remark" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="wechat_key" jdbcType="LONGVARCHAR" property="wechatKey" />
<result column="refund_cert" jdbcType="LONGVARBINARY" property="refundCert" />
</resultMap>
</mapper>
\ No newline at end of file
package com.onsiteservice.entity.pay;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@ApiModel("微信支付商户表")
@Table(name = "pay_channel_wechat")
public class PayChannelWechat implements Serializable {
/**
* 主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ApiModelProperty("主键")
private Long id;
/**
* 商户名称
*/
@Column(name = "merchant_name")
@ApiModelProperty("商户名称")
private String merchantName;
/**
* appid
*/
@ApiModelProperty("appid")
private String appid;
/**
* 商户id
*/
@ApiModelProperty("商户id")
private String mchid;
/**
* 备注
*/
@ApiModelProperty("备注")
private String remark;
/**
* 创建时间
*/
@Column(name = "create_time")
@ApiModelProperty("创建时间")
private Date createTime;
/**
* 秘钥
*/
@Column(name = "wechat_key")
@ApiModelProperty("秘钥")
private String wechatKey;
/**
* 退款证书
*/
@Column(name = "refund_cert")
@ApiModelProperty("退款证书")
private byte[] refundCert;
private static final long serialVersionUID = 1L;
}
\ No newline at end of file
package com.onsiteservice.miniapp.controller.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Map;
@Getter
@Setter
@ToString
@ApiModel(description = "订单支付参数")
public class OrderPayVO {
@ApiModelProperty("支付参数(key:appId,timeStamp,nonceStr,package,signType,paySign)")
private Map<String, String> payParam;
}
......@@ -4,11 +4,11 @@ package com.onsiteservice.miniapp.controller.weixn;
import cn.binarywang.wx.miniapp.api.WxMaService;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.onsiteservice.common.annotation.user.CurrentUserId;
import com.onsiteservice.common.service.dto.MiniQrCodeDTO;
import com.onsiteservice.core.result.Result;
import com.onsiteservice.miniapp.controller.weixn.dto.LoginDTO;
import com.onsiteservice.miniapp.controller.weixn.dto.WxEncryptDTO;
import com.onsiteservice.miniapp.service.weixin.WeiXinService;
import com.onsiteservice.miniapp.service.weixin.dto.MiniQrCodeDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.NonNull;
......
......@@ -6,6 +6,7 @@ import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import com.google.common.collect.ImmutableMap;
import com.onsiteservice.common.service.dto.MiniQrCodeDTO;
import com.onsiteservice.core.exception.ServiceException;
import com.onsiteservice.core.result.Result;
import com.onsiteservice.core.security.jwt.JwtManager;
......@@ -13,7 +14,6 @@ import com.onsiteservice.entity.user.User;
import com.onsiteservice.miniapp.controller.weixn.dto.LoginDTO;
import com.onsiteservice.miniapp.controller.weixn.dto.WxEncryptDTO;
import com.onsiteservice.miniapp.service.user.UserService;
import com.onsiteservice.miniapp.service.weixin.dto.MiniQrCodeDTO;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
......
package com.onsiteservice.miniapp.service.weixin.pay;
import com.onsiteservice.core.exception.ServiceException;
import com.onsiteservice.miniapp.service.weixin.pay.config.*;
import com.onsiteservice.miniapp.service.weixin.pay.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* @author: qiaobin
* @description:微信网页扫码支付
* @date: 2019/1/22
*/
public class WechatNativePay {
private static Logger logger = LoggerFactory.getLogger(WechatNativePay.class);
WechatPayMyConfigMini configMini;
WechatPayMini wxpayMini;
public WechatNativePay(String appID, String mchID, String key) {
configMini = new WechatPayMyConfigMini(appID, mchID, key);
wxpayMini = new WechatPayMini(configMini);
}
public Map<String, String> getPayParam(String body, String out_trade_no, BigDecimal total_fee, String notifyUrl, String openId, HttpServletRequest request) throws Exception {
//微信支付
HashMap<String, String> payParam = new HashMap<>();
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String prepayId = getPrepayId(body, out_trade_no, total_fee, notifyUrl, openId, request);
payParam.put("appId", configMini.getAppID());
payParam.put("timeStamp", timestamp);
payParam.put("nonceStr", UUIDGenerator.generate());
payParam.put("package", "prepay_id=" + prepayId);
payParam.put("signType", "MD5");
String paySign = WXPayUtil.generateSignature(payParam, configMini.getKey());
payParam.put("paySign", paySign);
return payParam;
}
/**
* 生成预支付交易单
*
* @param body 商品或支付单简要描述 32个字符
* @param out_trade_no 商户系统内部的订单号,32个字符内
* @param total_fee 支付金额单位为【分】
* @param notifyUrl
* @param request
* @return
* @throws Exception
*/
public String getPrepayId(String body, String out_trade_no, BigDecimal total_fee, String notifyUrl, String openId, HttpServletRequest request) throws Exception {
logger.info("===getPrepayId 参数=====" + body + "|" + out_trade_no + "|" + total_fee.toString() + "|" + notifyUrl + "|" + openId);
String prepayId = "";
total_fee = total_fee.multiply(new BigDecimal(100));//微信支付金额需要乘以100
Map<String, String> data = new HashMap<String, String>();
data.put("body", body);
data.put("appid", configMini.getAppID());
data.put("mch_id", configMini.getMchID());
data.put("out_trade_no", out_trade_no);
data.put("fee_type", "CNY");
data.put("openid", openId);
data.put("total_fee", String.valueOf(total_fee.intValue()));
data.put("spbill_create_ip", IPUtil.getIpAddress(request));
data.put("notify_url", notifyUrl);
data.put("trade_type", "JSAPI"); // 此处指定为小程序支付
data.put("nonce_str", UUIDGenerator.generate());
String s = MapUtil.FormatBizQueryParaMap(data, false);
String buildSign = s + "&key=" + configMini.getKey();
String sign = MD5Util.MD5(buildSign).toUpperCase();
data.put("sign", sign);
Map<String, String> respXml = wxpayMini.unifiedOrder(data);
logger.info("===获取预支付id,返回xml数据=====" + respXml);
String returnCode = respXml.get("return_code");
String resultCode = respXml.get("result_code");
if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)) {
prepayId = respXml.get("prepay_id");
} else {
throw new RuntimeException("生成预支付id时未成功.");
}
return prepayId;
}
/**
* 生成退款交易单
*
* @param out_trade_no 商户系统内部的订单号,32个字符内
* @param out_refund_no 商户系统内部的退款订单号,32个字符内
* @param total_fee 支付金额单位为【分】
* @return
* @throws Exception
*/
public String doRefundMini(String out_trade_no, String out_refund_no, BigDecimal total_fee, BigDecimal refund_fee, String refundNotifyUrl, byte[] certData) throws Exception {
String appId = configMini.getAppID();
String mchId = configMini.getMchID();
String wechatKey = configMini.getKey();
String resultCode = "";
HashMap<String, String> prepayMap = new HashMap<>();
prepayMap.put("appid", appId);
prepayMap.put("nonce_str", UUIDUtil.createUUID());
prepayMap.put("mch_id", mchId);
prepayMap.put("notify_url", refundNotifyUrl);
prepayMap.put("out_trade_no", out_trade_no);
prepayMap.put("out_refund_no", out_refund_no);
total_fee = total_fee.multiply(new BigDecimal(100)); //重要,微信支付金额是分,前端传来是支付金额单位是元,必须扩大100倍
refund_fee = refund_fee.multiply(new BigDecimal(100)); //重要,微信支付金额是分,前端传来是支付金额单位是元,必须扩大100倍
prepayMap.put("total_fee", String.valueOf(total_fee.intValue()));
prepayMap.put("refund_fee", String.valueOf(refund_fee.intValue()));
String s = MapUtil.FormatBizQueryParaMap(prepayMap, false);
String buildSign = s + "&key=" + wechatKey;
String sign = MD5Util.MD5(buildSign).toUpperCase();
prepayMap.put("sign", sign);
logger.info("===发起退款操作,参数=====appId" + appId);
logger.info("===发起退款操作,参数=====nonce_str" + prepayMap.get("nonce_str"));
logger.info("===发起退款操作,参数=====mch_id" + prepayMap.get("mch_id"));
logger.info("===发起退款操作,参数=====notify_url" + prepayMap.get("notify_url"));
logger.info("===发起退款操作,参数=====out_trade_no" + prepayMap.get("out_trade_no"));
logger.info("===发起退款操作,参数=====out_refund_no" + prepayMap.get("out_refund_no"));
logger.info("===发起退款操作,参数=====total_fee" + prepayMap.get("total_fee"));
logger.info("===发起退款操作,参数=====refund_fee" + prepayMap.get("refund_fee"));
logger.info("===发起退款操作,参数=====sign" + prepayMap.get("sign"));
String sendXml = MapUtil.ArrayToXml(prepayMap);
try {
String returnXml = ClientCustomSSL.doRefund(new ByteArrayInputStream(certData), WXPayConstants.WXREFUND_URL, sendXml, mchId);
logger.info("===发起退款操作,返回xml数据=====" + returnXml);
String returnCode = ParseXmlUtil.parseXml2KeyValue(returnXml, "return_code");
resultCode = ParseXmlUtil.parseXml2KeyValue(returnXml, "result_code");
if (!"SUCCESS".equals(resultCode) || !"SUCCESS".equals(returnCode)) {
throw new ServiceException("生成退款交易单时未成功.");
}
} catch (Throwable e) {
e.printStackTrace();
logger.info("=====发起退款异常====" + e.getMessage());
throw new ServiceException(e.getMessage());
}
return resultCode;
}
}
package com.onsiteservice.miniapp.service.weixin.pay.config;
/**
* 域名管理,实现主备域名自动切换
*/
public abstract interface IWXPayDomain {
/**
* 上报域名网络状况
* @param domain 域名。 比如:api.mch.weixin.qq.com
* @param elapsedTimeMillis 耗时
* @param ex 网络请求中出现的异常。
* null表示没有异常
* ConnectTimeoutException,表示建立网络连接异常
* UnknownHostException, 表示dns解析异常
*/
abstract void report(final String domain, long elapsedTimeMillis, final Exception ex);
/**
* 获取域名
* @param config 配置
* @return 域名
*/
abstract DomainInfo getDomain(final WXPayConfig config);
static class DomainInfo{
public String domain; //域名
public boolean primaryDomain; //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名
public DomainInfo(String domain, boolean primaryDomain) {
this.domain = domain;
this.primaryDomain = primaryDomain;
}
@Override
public String toString() {
return "DomainInfo{" +
"domain='" + domain + '\'' +
", primaryDomain=" + primaryDomain +
'}';
}
}
}
\ No newline at end of file
package com.onsiteservice.miniapp.service.weixin.pay.config;
import java.io.InputStream;
public abstract class WXPayConfig {
/**
* 获取 App ID
*
* @return App ID
*/
public abstract String getAppID();
/**
* 获取 Mch ID
*
* @return Mch ID
*/
abstract String getMchID();
/**
* 获取 API 密钥
*
* @return API密钥
*/
abstract String getKey();
/**
* 获取商户证书内容
*
* @return 商户证书内容
*/
abstract InputStream getCertStream();
/**
* HTTP(S) 连接超时时间,单位毫秒
*
* @return
*/
public int getHttpConnectTimeoutMs() {
return 6*1000;
}
/**
* HTTP(S) 读数据超时时间,单位毫秒
*
* @return
*/
public int getHttpReadTimeoutMs() {
return 8*1000;
}
/**
* 获取WXPayDomain, 用于多域名容灾自动切换
* @return
*/
abstract IWXPayDomain getWXPayDomain();
/**
* 是否自动上报。
* 若要关闭自动上报,子类中实现该函数返回 false 即可。
*
* @return
*/
public boolean shouldAutoReport() {
return true;
}
/**
* 进行健康上报的线程的数量
*
* @return
*/
public int getReportWorkerNum() {
return 6;
}
/**
* 健康上报缓存消息的最大数量。会有线程去独立上报
* 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受
*
* @return
*/
public int getReportQueueMaxSize() {
return 10000;
}
/**
* 批量上报,一次最多上报多个数据
*
* @return
*/
public int getReportBatchSize() {
return 10;
}
}
package com.onsiteservice.miniapp.service.weixin.pay.config;
import org.apache.http.client.HttpClient;
/**
* 常量
*/
public class WXPayConstants {
public static final String DOMAIN_API = "api.mch.weixin.qq.com";
public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
public static final String FAIL = "FAIL";
public static final String SUCCESS = "SUCCESS";
public static final String HMACSHA256 = "HMAC-SHA256";
public static final String MD5 = "MD5";
public static final String FIELD_SIGN = "sign";
public static final String FIELD_SIGN_TYPE = "sign_type";
public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
public static final String USER_AGENT = WXPAYSDK_VERSION +
" (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
public static final String MICROPAY_URL_SUFFIX = "/pay/micropay";
public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
public static final String ORDERQUERY_URL_SUFFIX = "/pay/orderquery";
public static final String REVERSE_URL_SUFFIX = "/secapi/pay/reverse";
public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder";
public static final String REFUND_URL_SUFFIX = "/secapi/pay/refund";
public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery";
public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
public static final String REPORT_URL_SUFFIX = "/payitil/report";
public static final String SHORTURL_URL_SUFFIX = "/tools/shorturl";
public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
/**
* 退款接口链接
*/
public static final String WXREFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
/**
* 支付通知
*/
public static final String NOTIFY_URL = "http://39.96.19.25:8091/wechat/notify";
/**
* 退款通知
*/
public static final String REFUND_NOTIFY_URL = "http://39.96.19.25:8091/wechat/refundNotify";
/**
* 退款证书路径
*/
// public static final String WX_CERT_PATH = "D:\\cert\\antaitiyu\\apiclient_cert.p12";
public static final String WX_CERT_PATH = "/usr/deploy/cert/wechat/apiclient_cert.p12";
public enum SignType {
MD5, HMACSHA256
}
}
package com.onsiteservice.miniapp.service.weixin.pay.config;
import org.apache.http.conn.ConnectTimeoutException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
public class WXPayDomainSimpleImpl implements IWXPayDomain {
private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000; //3 minutes
private long switchToAlternateDomainTime = 0;
private Map<String, DomainStatics> domainData = new HashMap<String, DomainStatics>();
private WXPayDomainSimpleImpl(){}
public static IWXPayDomain instance(){
return WxpayDomainHolder.holder;
}
public synchronized void report(final String domain, long elapsedTimeMillis, final Exception ex) {
DomainStatics info = domainData.get(domain);
if(info == null){
info = new DomainStatics(domain);
domainData.put(domain, info);
}
if(ex == null){ //success
if(info.succCount >= 2){ //continue succ, clear error count
info.connectTimeoutCount = info.dnsErrorCount = info.otherErrorCount = 0;
}else{
++info.succCount;
}
}else if(ex instanceof ConnectTimeoutException){
info.succCount = info.dnsErrorCount = 0;
++info.connectTimeoutCount;
}else if(ex instanceof UnknownHostException){
info.succCount = 0;
++info.dnsErrorCount;
}else{
info.succCount = 0;
++info.otherErrorCount;
}
}
public synchronized DomainInfo getDomain(final WXPayConfig config) {
DomainStatics primaryDomain = domainData.get(WXPayConstants.DOMAIN_API);
if(primaryDomain == null ||
primaryDomain.isGood()) {
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
long now = System.currentTimeMillis();
if(switchToAlternateDomainTime == 0){ //first switch
switchToAlternateDomainTime = now;
return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
}else if(now - switchToAlternateDomainTime < MIN_SWITCH_PRIMARY_MSEC){
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
if(alternateDomain == null ||
alternateDomain.isGood() ||
alternateDomain.badCount() < primaryDomain.badCount()){
return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
}else{
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
}else{ //force switch back
switchToAlternateDomainTime = 0;
primaryDomain.resetCount();
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
if(alternateDomain != null)
alternateDomain.resetCount();
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
}
private static class WxpayDomainHolder{
private static IWXPayDomain holder = new WXPayDomainSimpleImpl();
}
static class DomainStatics {
final String domain;
int succCount = 0;
int connectTimeoutCount = 0;
int dnsErrorCount =0;
int otherErrorCount = 0;
DomainStatics(String domain) {
this.domain = domain;
}
void resetCount(){
succCount = connectTimeoutCount = dnsErrorCount = otherErrorCount = 0;
}
boolean isGood(){ return connectTimeoutCount <= 2 && dnsErrorCount <= 2; }
int badCount(){
return connectTimeoutCount + dnsErrorCount * 5 + otherErrorCount / 4;
}
}
}
\ No newline at end of file
package com.onsiteservice.miniapp.service.weixin.pay.config;
import com.onsiteservice.miniapp.service.weixin.pay.util.WXPayUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
/**
* 交易保障
*/
public class WXPayReport {
private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default";
private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000;
// private static final String REPORT_URL = "http://127.0.0.1:5000/test";
private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000;
private volatile static WXPayReport INSTANCE;
private LinkedBlockingQueue<String> reportMsgQueue = null;
private WXPayConfig config;
private ExecutorService executorService;
private WXPayReport(final WXPayConfig config) {
this.config = config;
reportMsgQueue = new LinkedBlockingQueue<String>(config.getReportQueueMaxSize());
// 添加处理线程
executorService = Executors.newFixedThreadPool(config.getReportWorkerNum(), new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
});
if (config.shouldAutoReport()) {
WXPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum());
for (int i = 0; i < config.getReportWorkerNum(); ++i) {
executorService.execute(new Runnable() {
public void run() {
while (true) {
// 先用 take 获取数据
try {
StringBuffer sb = new StringBuffer();
String firstMsg = reportMsgQueue.take();
WXPayUtil.getLogger().info("get first report msg: {}", firstMsg);
String msg = null;
sb.append(firstMsg); //会阻塞至有消息
int remainNum = config.getReportBatchSize() - 1;
for (int j=0; j<remainNum; ++j) {
WXPayUtil.getLogger().info("try get remain report msg");
// msg = reportMsgQueue.poll(); // 不阻塞了
msg = reportMsgQueue.take();
WXPayUtil.getLogger().info("get remain report msg: {}", msg);
if (msg == null) {
break;
}
else {
sb.append("\n");
sb.append(msg);
}
}
// 上报
WXPayReport.httpRequest(sb.toString(), DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
}
catch (Exception ex) {
WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
}
}
}
});
}
}
}
/**
* 单例,双重校验,请在 JDK 1.5及更高版本中使用
*
* @param config
* @return
*/
public static WXPayReport getInstance(WXPayConfig config) {
if (INSTANCE == null) {
synchronized (WXPayReport.class) {
if (INSTANCE == null) {
INSTANCE = new WXPayReport(config);
}
}
}
return INSTANCE;
}
/**
* http 请求
* @param data
* @param connectTimeoutMs
* @param readTimeoutMs
* @return
* @throws Exception
*/
private static String httpRequest(String data, int connectTimeoutMs, int readTimeoutMs) throws Exception {
BasicHttpClientConnectionManager connManager;
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build(),
null,
null,
null
);
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connManager)
.build();
HttpPost httpPost = new HttpPost(REPORT_URL);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT);
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
return EntityUtils.toString(httpEntity, "UTF-8");
}
public void report(String uuid, long elapsedTimeMillis,
String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis,
boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
long currentTimestamp = WXPayUtil.getCurrentTimestamp();
ReportInfo reportInfo = new ReportInfo(uuid, currentTimestamp, elapsedTimeMillis,
firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout);
String data = reportInfo.toLineString(config.getKey());
WXPayUtil.getLogger().info("report {}", data);
if (data != null) {
reportMsgQueue.offer(data);
}
}
@Deprecated
private void reportSync(final String data) throws Exception {
httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
}
@Deprecated
private void reportAsync(final String data) throws Exception {
new Thread(new Runnable() {
public void run() {
try {
httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
}
catch (Exception ex) {
WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
}
}
}).start();
}
public static class ReportInfo {
/**
* 布尔变量使用int。0为false, 1为true。
*/
// 基本信息
private String version = "v1";
private String sdk = WXPayConstants.WXPAYSDK_VERSION;
private String uuid; // 交易的标识
private long timestamp; // 上报时的时间戳,单位秒
private long elapsedTimeMillis; // 耗时,单位 毫秒
// 针对主域名
private String firstDomain; // 第1次请求的域名
private boolean primaryDomain; //是否主域名
private int firstConnectTimeoutMillis; // 第1次请求设置的连接超时时间,单位 毫秒
private int firstReadTimeoutMillis; // 第1次请求设置的读写超时时间,单位 毫秒
private int firstHasDnsError; // 第1次请求是否出现dns问题
private int firstHasConnectTimeout; // 第1次请求是否出现连接超时
private int firstHasReadTimeout; // 第1次请求是否出现连接超时
public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
this.uuid = uuid;
this.timestamp = timestamp;
this.elapsedTimeMillis = elapsedTimeMillis;
this.firstDomain = firstDomain;
this.primaryDomain = primaryDomain;
this.firstConnectTimeoutMillis = firstConnectTimeoutMillis;
this.firstReadTimeoutMillis = firstReadTimeoutMillis;
this.firstHasDnsError = firstHasDnsError?1:0;
this.firstHasConnectTimeout = firstHasConnectTimeout?1:0;
this.firstHasReadTimeout = firstHasReadTimeout?1:0;
}
@Override
public String toString() {
return "ReportInfo{" +
"version='" + version + '\'' +
", sdk='" + sdk + '\'' +
", uuid='" + uuid + '\'' +
", timestamp=" + timestamp +
", elapsedTimeMillis=" + elapsedTimeMillis +
", firstDomain='" + firstDomain + '\'' +
", primaryDomain=" + primaryDomain +
", firstConnectTimeoutMillis=" + firstConnectTimeoutMillis +
", firstReadTimeoutMillis=" + firstReadTimeoutMillis +
", firstHasDnsError=" + firstHasDnsError +
", firstHasConnectTimeout=" + firstHasConnectTimeout +
", firstHasReadTimeout=" + firstHasReadTimeout +
'}';
}
/**
* 转换成 csv 格式
*
* @return
*/
public String toLineString(String key) {
String separator = ",";
Object[] objects = new Object[] {
version, sdk, uuid, timestamp, elapsedTimeMillis,
firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout
};
StringBuffer sb = new StringBuffer();
for(Object obj: objects) {
sb.append(obj).append(separator);
}
try {
String sign = WXPayUtil.HMACSHA256(sb.toString(), key);
sb.append(sign);
return sb.toString();
}
catch (Exception ex) {
return null;
}
}
}
}
package com.onsiteservice.miniapp.service.weixin.pay.config;
import com.onsiteservice.miniapp.service.weixin.pay.util.WXPayUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.SecureRandom;
public class WXPayRequest {
private WXPayConfig config;
public WXPayRequest(WXPayConfig config){
this.config = config;
}
/**
* 请求,只请求一次,不做重试
* @param domain
* @param urlSuffix
* @param uuid
* @param data
* @param connectTimeoutMs
* @param readTimeoutMs
* @param useCert 是否使用证书,针对退款、撤销等操作
* @return
* @throws Exception
*/
private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
BasicHttpClientConnectionManager connManager;
if (useCert) {
// 证书
char[] password = config.getMchID().toCharArray();
InputStream certStream = config.getCertStream();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certStream, password);
// 实例化密钥库 & 初始化密钥工厂
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password);
// 创建 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
sslContext,
new String[]{"TLSv1"},
null,
new org.apache.http.conn.ssl.DefaultHostnameVerifier());
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build(),
null,
null,
null
);
}
else {
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build(),
null,
null,
null
);
}
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connManager)
.build();
String url = "https://" + domain + urlSuffix;
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + config.getMchID());
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
return EntityUtils.toString(httpEntity, "UTF-8");
}
private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
Exception exception = null;
long elapsedTimeMillis = 0;
long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
boolean firstHasDnsErr = false;
boolean firstHasConnectTimeout = false;
boolean firstHasReadTimeout = false;
IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
if(domainInfo == null){
throw new RuntimeException("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
}
try {
String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
return result;
}
catch (UnknownHostException ex) { // dns 解析错误,或域名不存在
exception = ex;
firstHasDnsErr = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout
);
}
catch (ConnectTimeoutException ex) {
exception = ex;
firstHasConnectTimeout = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout
);
}
catch (SocketTimeoutException ex) {
exception = ex;
firstHasReadTimeout = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
}
catch (Exception ex) {
exception = ex;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
}
config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
throw exception;
}
/**
* 可重试的,非双向认证的请求
* @param urlSuffix
* @param uuid
* @param data
* @return
*/
public String requestWithoutCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), false, autoReport);
}
/**
* 可重试的,非双向认证的请求
* @param urlSuffix
* @param uuid
* @param data
* @param connectTimeoutMs
* @param readTimeoutMs
* @return
*/
public String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
}
/**
* 可重试的,双向认证的请求
* @param urlSuffix
* @param uuid
* @param data
* @return
*/
public String requestWithCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), true, autoReport);
}
/**
* 可重试的,双向认证的请求
* @param urlSuffix
* @param uuid
* @param data
* @param connectTimeoutMs
* @param readTimeoutMs
* @return
*/
public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
}
}
package com.onsiteservice.miniapp.service.weixin.pay.config;
import com.onsiteservice.miniapp.service.weixin.pay.util.WXPayUtil;
import java.util.Map;
public class WechatPayMini {
private WXPayConstants.SignType signType = WXPayConstants.SignType.MD5;
private boolean autoReport;
private WXPayConfig config;
private WXPayRequest wxPayRequest;
public WechatPayMini(){}
public WechatPayMini(final WXPayConfig config){
this.config = config;
this.wxPayRequest = new WXPayRequest(config);
}
/**
* 作用:统一下单<br>
* 场景:公共号支付、扫码支付、APP支付
* @param reqData 向wxpay post的请求数据
* @return API返回数据
* @throws Exception
*/
public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:统一下单<br>
* 场景:公共号支付、扫码支付、APP支付
* @param reqData 向wxpay post的请求数据
* @param connectTimeoutMs 连接超时时间,单位是毫秒
* @param readTimeoutMs 读超时时间,单位是毫秒
* @return API返回数据
* @throws Exception
*/
public Map<String, String> unifiedOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
return this.processResponseXml(respXml);
}
/**
* 不需要证书的请求
* @param urlSuffix String
* @param reqData 向wxpay post的请求数据
* @param connectTimeoutMs 超时时间,单位是毫秒
* @param readTimeoutMs 超时时间,单位是毫秒
* @return API返回数据
* @throws Exception
*/
public String requestWithoutCert(String urlSuffix, Map<String, String> reqData,
int connectTimeoutMs, int readTimeoutMs) throws Exception {
String msgUUID = reqData.get("nonce_str");
String reqBody = WXPayUtil.mapToXml(reqData);
String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
return resp;
}
/**
* 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
* 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
*
* @param reqData
* @return
* @throws Exception
*/
public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
reqData.put("appid", config.getAppID());
reqData.put("mch_id", config.getMchID());
reqData.put("nonce_str", WXPayUtil.generateNonceStr());
if (WXPayConstants.SignType.MD5.equals(this.signType)) {
reqData.put("sign_type", WXPayConstants.MD5);
}
else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) {
reqData.put("sign_type", WXPayConstants.HMACSHA256);
}
reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
return reqData;
}
/**
* 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
* @param xmlStr API返回的XML格式数据
* @return Map类型数据
* @throws Exception
*/
public Map<String, String> processResponseXml(String xmlStr) throws Exception {
String RETURN_CODE = "return_code";
String return_code;
Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
if (respData.containsKey(RETURN_CODE)) {
return_code = respData.get(RETURN_CODE);
}
else {
throw new RuntimeException(String.format("No `return_code` in XML: %s", xmlStr));
}
if (return_code.equals(WXPayConstants.FAIL)) {
return respData;
}
else if (return_code.equals(WXPayConstants.SUCCESS)) {
if (this.isResponseSignatureValid(respData)) {
return respData;
}
else {
throw new RuntimeException(String.format("Invalid sign value in XML: %s", xmlStr));
}
}
else {
throw new RuntimeException(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
}
}
/**
* 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
*
* @param reqData 向wxpay post的请求数据
* @return 签名是否有效
* @throws Exception
*/
public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
// 返回数据的签名方式和请求中给定的签名方式是一致的
return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
}
}
package com.onsiteservice.miniapp.service.weixin.pay.config;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* @author: qiaobin
* @description:
* @date: 2019/1/22
*/
public class WechatPayMyConfigMini extends WXPayConfig {
private String appID;
private String mchID;
private String key;
public WechatPayMyConfigMini(String appID, String mchID, String key) {
this.appID = appID;
this.mchID = mchID;
this.key = key;
}
private byte[] certData;
public String getAppID() {
return this.appID;
}
public String getMchID() {
return this.mchID;
}
public String getKey() {
return this.key;
}
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public int getHttpConnectTimeoutMs() {
return 8000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
@Override
public IWXPayDomain getWXPayDomain() {
return WXPayDomainSimpleImpl.instance();
}
public String getPrimaryDomain() {
return "api.mch.weixin.qq.com";
}
public String getAlternateDomain() {
return "api2.mch.weixin.qq.com";
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AESUtil {
/**
* 密钥算法
*/
private static final String ALGORITHM = "AES";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
/**
* AES解密
*
* @param base64Data
* @return
* @throws Exception
*/
public static String decryptData(String base64Data, String wechatKey) throws Exception {
SecretKeySpec key = new SecretKeySpec(MD5Util.MD5Encode(wechatKey, "UTF-8").toLowerCase().getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64Util.decode(base64Data)));
}
public static void main(String[] args) throws Exception {
String wechatKey = "9005f5ab73de0c2e10466b5459c966e7";
String A = "o66B2HUv0rQYII6BZv2Is7Zyo7WabgeAOZbqXwzp9iBC6dfY/VKDsffUo0Wt9Q4po6U5C7blttp7dW4Z3Ya7TJn477ZOyc5QltTgh2F5uv4R/bHocFofPvtGC6tTPY9+SJaEljqgcv3m0ZgAsuNPuDDyy/jlzgSvG67B8MKE3mrnFU/V+IYPTp9jHOHjy2wHOMaFR3u8elAhgThW9MSkboJ4BmTFmdP1qnsLaQ5uv5g5GF8DyUbn+XbzEtMyGzWLqDJCbH4aTCoWbwbWemyzZVH+ngQkuBUmRXwHUzIM/N3QHuiMvH4976/xpqH/CPnCdph6BxEZj2CHLQnVwSxhvQagORtrZVKzBVr1gd1XDarr6gXMv3u4RxoX6IHkZEQmrEvetx9Huzm4DkKgdXzwR3Uu3w+LddUGJssoHHA25V0dHo/K8assCfVnoX/BX5K+YCfkcIlKAV87OnfVrIlYUwNptDCg1Und84ObF4e8ApbBYfx9hPPgaApi7sgmkWWOWZdA6AnhL2C6i/2Ndryv+kV2PrDODi/8cstnmhTcYdN3j/Kt000iQbCX/sNukEixAXE1Wre490glsRFQFVEdNVINxFfYIbIh9CbJ02oO2xWqq58/oQA/rYmTko2nzOn5I7I4mJxyWadEiPfymdMze418HD77o3uSF5aG8xzmsA+63wMkPWRlnika5mD9r0PGK3fPY7sLBnbAgd5dppNj56pVhNs+ekjEeIvjJuP0bV3lJvGR+vnNu6BeFeI0Qmoy3cGUOxxcc2bMn2pYX+TJlqqN4O1BzDXIlrvcabniIf0NDVw5K0mN/Mfpmeij2h70yPVofp5iLnBEFHmY7HNKIbdChUK7ydDo5FDcqQGhRA+IE+A0atJVOoq2p5/UOhBJawbMg2vxKFKrQpKiAVbpbKqCFgdb+TCTdNFqZ0zRQ4tz53BomETs8HYaTebDPfbNXV+O/cU8Es5DbjFVW6zOjD9mE2iYygdDyeex5HSfgXpoJdWiOd5c2d5YbAOaN1zZmEFRq6DatOM2S6Zl5mJTvsiaRVr/UgJrZD4pDDPas7E=";
String B = AESUtil.decryptData(A,wechatKey);
System.out.println(B);
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
public class Base64Util {
private static final char S_BASE64CHAR[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
private static final byte S_DECODETABLE[];
static {
S_DECODETABLE = new byte[128];
for (int i = 0; i < S_DECODETABLE.length; i++)
S_DECODETABLE[i] = 127;
for (int i = 0; i < S_BASE64CHAR.length; i++)
S_DECODETABLE[S_BASE64CHAR[i]] = (byte) i;
}
/**
* @param ibuf
* @param obuf
* @param wp
* @return
*/
private static int decode0(char ibuf[], byte obuf[], int wp) {
int outlen = 3;
if (ibuf[3] == '=')
outlen = 2;
if (ibuf[2] == '=')
outlen = 1;
int b0 = S_DECODETABLE[ibuf[0]];
int b1 = S_DECODETABLE[ibuf[1]];
int b2 = S_DECODETABLE[ibuf[2]];
int b3 = S_DECODETABLE[ibuf[3]];
switch (outlen) {
case 1: // '\001'
obuf[wp] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
return 1;
case 2: // '\002'
obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
obuf[wp] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15);
return 2;
case 3: // '\003'
obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
obuf[wp++] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15);
obuf[wp] = (byte) (b2 << 6 & 192 | b3 & 63);
return 3;
}
throw new RuntimeException("Internal error");
}
/**
* @param data
* @param off
* @param len
* @return
*/
public static byte[] decode(char data[], int off, int len) {
char ibuf[] = new char[4];
int ibufcount = 0;
byte obuf[] = new byte[(len / 4) * 3 + 3];
int obufcount = 0;
for (int i = off; i < off + len; i++) {
char ch = data[i];
if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
continue;
ibuf[ibufcount++] = ch;
if (ibufcount == ibuf.length) {
ibufcount = 0;
obufcount += decode0(ibuf, obuf, obufcount);
}
}
if (obufcount == obuf.length) {
return obuf;
} else {
byte ret[] = new byte[obufcount];
System.arraycopy(obuf, 0, ret, 0, obufcount);
return ret;
}
}
/**
* @param data
* @return
*/
public static byte[] decode(String data) {
char ibuf[] = new char[4];
int ibufcount = 0;
byte obuf[] = new byte[(data.length() / 4) * 3 + 3];
int obufcount = 0;
for (int i = 0; i < data.length(); i++) {
char ch = data.charAt(i);
if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
continue;
ibuf[ibufcount++] = ch;
if (ibufcount == ibuf.length) {
ibufcount = 0;
obufcount += decode0(ibuf, obuf, obufcount);
}
}
if (obufcount == obuf.length) {
return obuf;
} else {
byte ret[] = new byte[obufcount];
System.arraycopy(obuf, 0, ret, 0, obufcount);
return ret;
}
}
/**
* @param data
* @param off
* @param len
* @param ostream
* @throws IOException
*/
public static void decode(char data[], int off, int len, OutputStream ostream) throws IOException {
char ibuf[] = new char[4];
int ibufcount = 0;
byte obuf[] = new byte[3];
for (int i = off; i < off + len; i++) {
char ch = data[i];
if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
continue;
ibuf[ibufcount++] = ch;
if (ibufcount == ibuf.length) {
ibufcount = 0;
int obufcount = decode0(ibuf, obuf, 0);
ostream.write(obuf, 0, obufcount);
}
}
}
/**
* @param data
* @param ostream
* @throws IOException
*/
public static void decode(String data, OutputStream ostream) throws IOException {
char ibuf[] = new char[4];
int ibufcount = 0;
byte obuf[] = new byte[3];
for (int i = 0; i < data.length(); i++) {
char ch = data.charAt(i);
if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
continue;
ibuf[ibufcount++] = ch;
if (ibufcount == ibuf.length) {
ibufcount = 0;
int obufcount = decode0(ibuf, obuf, 0);
ostream.write(obuf, 0, obufcount);
}
}
}
/**
* @param data
* @return
*/
public static String encode(byte data[]) {
return encode(data, 0, data.length);
}
/**
* @param data
* @param off
* @param len
* @return
*/
public static String encode(byte data[], int off, int len) {
if (len <= 0)
return "";
char out[] = new char[(len / 3) * 4 + 4];
int rindex = off;
int windex = 0;
int rest;
for (rest = len - off; rest >= 3; rest -= 3) {
int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
out[windex++] = S_BASE64CHAR[i >> 18];
out[windex++] = S_BASE64CHAR[i >> 12 & 63];
out[windex++] = S_BASE64CHAR[i >> 6 & 63];
out[windex++] = S_BASE64CHAR[i & 63];
rindex += 3;
}
if (rest == 1) {
int i = data[rindex] & 255;
out[windex++] = S_BASE64CHAR[i >> 2];
out[windex++] = S_BASE64CHAR[i << 4 & 63];
out[windex++] = '=';
out[windex++] = '=';
} else if (rest == 2) {
int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
out[windex++] = S_BASE64CHAR[i >> 10];
out[windex++] = S_BASE64CHAR[i >> 4 & 63];
out[windex++] = S_BASE64CHAR[i << 2 & 63];
out[windex++] = '=';
}
return new String(out, 0, windex);
}
/**
* @param data
* @param off
* @param len
* @param ostream
* @throws IOException
*/
public static void encode(byte data[], int off, int len, OutputStream ostream) throws IOException {
if (len <= 0)
return;
byte out[] = new byte[4];
int rindex = off;
int rest;
for (rest = len - off; rest >= 3; rest -= 3) {
int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
out[0] = (byte) S_BASE64CHAR[i >> 18];
out[1] = (byte) S_BASE64CHAR[i >> 12 & 63];
out[2] = (byte) S_BASE64CHAR[i >> 6 & 63];
out[3] = (byte) S_BASE64CHAR[i & 63];
ostream.write(out, 0, 4);
rindex += 3;
}
if (rest == 1) {
int i = data[rindex] & 255;
out[0] = (byte) S_BASE64CHAR[i >> 2];
out[1] = (byte) S_BASE64CHAR[i << 4 & 63];
out[2] = 61;
out[3] = 61;
ostream.write(out, 0, 4);
} else if (rest == 2) {
int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
out[0] = (byte) S_BASE64CHAR[i >> 10];
out[1] = (byte) S_BASE64CHAR[i >> 4 & 63];
out[2] = (byte) S_BASE64CHAR[i << 2 & 63];
out[3] = 61;
ostream.write(out, 0, 4);
}
}
/**
* @param data
* @param off
* @param len
* @param writer
* @throws IOException
*/
public static void encode(byte data[], int off, int len, Writer writer) throws IOException {
if (len <= 0)
return;
char out[] = new char[4];
int rindex = off;
int rest = len - off;
int output = 0;
do {
if (rest < 3)
break;
int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
out[0] = S_BASE64CHAR[i >> 18];
out[1] = S_BASE64CHAR[i >> 12 & 63];
out[2] = S_BASE64CHAR[i >> 6 & 63];
out[3] = S_BASE64CHAR[i & 63];
writer.write(out, 0, 4);
rindex += 3;
rest -= 3;
if ((output += 4) % 76 == 0)
writer.write("\n");
}
while (true);
if (rest == 1) {
int i = data[rindex] & 255;
out[0] = S_BASE64CHAR[i >> 2];
out[1] = S_BASE64CHAR[i << 4 & 63];
out[2] = '=';
out[3] = '=';
writer.write(out, 0, 4);
} else if (rest == 2) {
int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
out[0] = S_BASE64CHAR[i >> 10];
out[1] = S_BASE64CHAR[i >> 4 & 63];
out[2] = S_BASE64CHAR[i << 2 & 63];
out[3] = '=';
writer.write(out, 0, 4);
}
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
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 javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.security.KeyStore;
/**
* @author wang jianguo
* @date 2020/10/20 15:15
* @email qizhi1a440@outlook.com
* @description:
*/
public class ClientCustomSSL {
/**
* @param url 微信接口路径
* @param data
* @param mchIdStr
* @return
* @throws Exception
*/
public static String doRefund(InputStream wxCert, String url, String data, String mchIdStr) throws Exception {
/**
* 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
char[] mchId = mchIdStr.toCharArray();
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//FileInputStream instream = new FileInputStream(new File(wxCertPath));//P12文件在服务器磁盘中的目录
try {
/**
* 此处要改成你的MCHID
* */
keyStore.load(wxCert, mchId);//这里写密码..默认是你的MCHID
} finally {
wxCert.close();
}
// Trust own CA and all self-signed certs
/**
* 此处要改成你的MCHID
* */
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, mchId)//这里也是写密码的
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
try {
HttpPost httpost = new HttpPost(url); // 设置响应头信息
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import com.common.util.Utils;
import javax.servlet.http.HttpServletRequest;
/**
*
* @description 获取客户端IP
* @author wangyu
*
*/
public class IPUtil {
public static String getIpAddress(HttpServletRequest request) {
return Utils.getIpAddress(request);
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util
{
/** The Constant S11. */
static final int S11 = 7;
/** The Constant S12. */
static final int S12 = 12;
/** The Constant S13. */
static final int S13 = 17;
/*
* 下面这些S11-S44实际上是一个4*4的矩阵,在原始的C实现中是用#define 实现的, 这里把它们实现成为static
* final是表示了只读,切能在同一个进程空间内的多个 Instance间共享
*/
/** The Constant S14. */
static final int S14 = 22;
/** The Constant S21. */
static final int S21 = 5;
/** The Constant S22. */
static final int S22 = 9;
/** The Constant S23. */
static final int S23 = 14;
/** The Constant S24. */
static final int S24 = 20;
/** The Constant S31. */
static final int S31 = 4;
/** The Constant S32. */
static final int S32 = 11;
/** The Constant S33. */
static final int S33 = 16;
/** The Constant S34. */
static final int S34 = 23;
/** The Constant S41. */
static final int S41 = 6;
/** The Constant S42. */
static final int S42 = 10;
/** The Constant S43. */
static final int S43 = 15;
/** The Constant S44. */
static final int S44 = 21;
/** The Constant PADDING. */
static final byte[] PADDING = { -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0 };
private static final String SALF_FINAL = "MD5PWD";
private static final int MD5_PASSWORD_TIME = 3;
private static final int MD5_EXPRESS_PASSWORD_TIME = 1;
/*
* 下面的三个成员是MD5计算过程中用到的3个核心数据,在原始的C实现中 被定义到MD5_CTX结构中
*/
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
/** The digest hex str. */
public String digestHexStr;
// first)
/** The state. */
private long[] state = new long[4]; // state (ABCD)
/*
* digestHexStr是MD5的唯一一个公共成员,是最新一次计算结果的   16进制ASCII表示.
*/
/** The count. */
private long[] count = new long[2]; // number of bits, modulo 2^64 (lsb
/*
* digest,是最新一次计算结果的2进制内部表示,表示128bit的MD5值.
*/
/** The buffer. */
private byte[] buffer = new byte[64]; // input buffer
/*
* getMD5ofStr是类MD5最主要的公共方法,入口参数是你想要进行MD5变换的字符串
* 返回的是变换完的结果,这个结果是从公共成员digestHexStr取得的.
*/
/** The digest. */
private byte[] digest = new byte[16];
// 这是MD5这个类的标准构造函数,JavaBean要求有一个public的并且没有参数的构造函数
/**
* Instantiates a new m d5 util.
*/
public MD5Util()
{
md5Init();
return;
}
/* md5Init是一个初始化函数,初始化核心变量,装入标准的幻数 */
/**
* B2iu.
*
* @param b the b
* @return the long
*/
public static long b2iu(byte b)
{
return b < 0 ? b & 0x7F + 128 : b;
}
/*
* F, G, H ,I 是4个基本的MD5函数,在原始的MD5的C实现中,由于它们是
* 简单的位运算,可能出于效率的考虑把它们实现成了宏,在java中,我们把它们   实现成了private方法,名字保持了原来C中的。
*/
/**
* Byte hex.
*
* @param ib the ib
* @return the string
*/
public static String byteHEX(byte ib)
{
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] ob = new char[2];
ob[0] = Digit[(ib >>> 4) & 0X0F];
ob[1] = Digit[ib & 0X0F];
String s = new String(ob);
return s;
}
/**
* To m d5.
*
* @param source the source
* @return the string
*/
public static String toMD5(String source)
{
MD5Util md5 = new MD5Util();
return md5.getMD5ofStr(source);
}
/**
* 多次加密
*
* @param source
* @param n 次数
* @return (大写)
*/
public static String MD5Times(String source, int n)
{
for (int i = 0; i < n; i++)
{
source = toMD5(source);
}
return source.toUpperCase();
}
/**
* MD5.
*
* @param password the password
* @return the string
*/
private static String Md5Password(String phone, String value, String password, int times)
{
StringBuffer sbuffer = new StringBuffer();
sbuffer.append(phone);
sbuffer.append(value);
sbuffer.append(password);
return MD5Times(sbuffer.toString(), times);
}
/*
* FF,GG,HH和II将调用F,G,H,I进行近一步变换 FF, GG, HH, and II transformations for
* rounds 1, 2, 3, and 4. Rotation is separate from addition to prevent
* recomputation.
*/
/**
* md5加密
*
* @param phone
* @param password
* @return
*/
public static String Md5Password(String phone, String password)
{
return Md5Password(phone, SALF_FINAL, password, MD5_PASSWORD_TIME);
}
/**
* 验证输入的手机号码和密码 是否和数据库保存的匹配
*
* @param phone
* @param inputpassword
* @param password
* @return true:正确
*/
public static boolean checkPassword(String phone, String inputpassword, String password)
{
return password.equals(Md5Password(phone, inputpassword));
}
/***
* MD5加码 生成32位md5码
*/
public static String string2MD5(String inStr){
MessageDigest md5 = null;
try{
md5 = MessageDigest.getInstance("MD5");
}catch (Exception e){
System.out.println(e.toString());
e.printStackTrace();
return "";
}
char[] charArray = inStr.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++){
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16)
hexValue.append("0");
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
/**
* 加密解密算法 执行一次加密,两次解密
*/
public static String convertMD5(String inStr){
char[] a = inStr.toCharArray();
for (int i = 0; i < a.length; i++){
a[i] = (char) (a[i] ^ 't');
}
String s = new String(a);
return s;
}
/*
* md5Update是MD5的主计算过程,inbuf是要变换的字节串,inputlen是长度,这个
* 函数由getMD5ofStr调用,调用之前需要调用md5init,因此把它设计成private的
*/
/**
* MD5加密字符串
* @param val
* @return
* @throws NoSuchAlgorithmException
*/
public static String MD5(String val) {
byte[] hash;
try {
hash = MessageDigest.getInstance("MD5").digest(val.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 should not be supported!", e);
}catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 should not be supported!", e);
}
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if((b & 0xFF) < 0x10) hex.append("0");
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
}
/*
* md5Final整理和填写输出结果
*/
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
/*
* md5Memcpy是一个内部使用的byte数组的块拷贝函数,从input的inpos开始把len长度的      
* 字节拷贝到output的outpos位置开始
*/
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
/*
* md5Transform是MD5核心变换程序,有md5Update调用,block是分块的原始字节
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
/*
* Encode把long数组按顺序拆成byte数组,因为java的long类型是64bit的, 只拆低32bit,以适应原始C实现的用途
*/
public static void main(String[] args)
{
System.out.println(MD5Util.string2MD5("qqqqqq"));
}
/*
* Decode把byte数组按顺序合成成long数组,因为java的long类型是64bit的,
* 只合成低32bit,高32bit清零,以适应原始C实现的用途
*/
/**
* Gets the m d5of str.
*
* @param inbuf the inbuf
* @return the m d5of str
*/
public String getMD5ofStr(String inbuf)
{
md5Init();
md5Update(inbuf.getBytes(), inbuf.length());
md5Final();
digestHexStr = "";
for (int i = 0; i < 16; i++)
{
digestHexStr += byteHEX(digest[i]);
}
return digestHexStr;
}
/*
* b2iu是我写的一个把byte按照不考虑正负号的原则的"升位"程序,因为java没有unsigned运算
*/
/**
* Md5 init.
*/
private void md5Init()
{
count[0] = 0L;
count[1] = 0L;
// /* Load magic initialization constants.
state[0] = 0x67452301L;
state[1] = 0xefcdab89L;
state[2] = 0x98badcfeL;
state[3] = 0x10325476L;
return;
}
/*
* byteHEX(),用来把一个byte类型的数转换成十六进制的ASCII表示,
*  因为java中的byte的toString无法实现这一点,我们又没有C语言中的 sprintf(outbuf,"%02X",ib)
*/
/**
* F.
*
* @param x the x
* @param y the y
* @param z the z
* @return the long
*/
private long F(long x, long y, long z)
{
return (x & y) | ((~x) & z);
}
/**
* G.
*
* @param x the x
* @param y the y
* @param z the z
* @return the long
*/
private long G(long x, long y, long z)
{
return (x & z) | (y & (~z));
}
/**
* H.
*
* @param x the x
* @param y the y
* @param z the z
* @return the long
*/
private long H(long x, long y, long z)
{
return x ^ y ^ z;
}
/**
* I.
*
* @param x the x
* @param y the y
* @param z the z
* @return the long
*/
private long I(long x, long y, long z)
{
return y ^ (x | (~z));
}
/**
* FF.
*
* @param a the a
* @param b the b
* @param c the c
* @param d the d
* @param x the x
* @param s the s
* @param ac the ac
* @return the long
*/
private long FF(long a, long b, long c, long d, long x, long s, long ac)
{
a += F(b, c, d) + x + ac;
a = ((int) a << s) | ((int) a >>> (32 - s));
a += b;
return a;
}
/**
* GG.
*
* @param a the a
* @param b the b
* @param c the c
* @param d the d
* @param x the x
* @param s the s
* @param ac the ac
* @return the long
*/
private long GG(long a, long b, long c, long d, long x, long s, long ac)
{
a += G(b, c, d) + x + ac;
a = ((int) a << s) | ((int) a >>> (32 - s));
a += b;
return a;
}
/**
* HH.
*
* @param a the a
* @param b the b
* @param c the c
* @param d the d
* @param x the x
* @param s the s
* @param ac the ac
* @return the long
*/
private long HH(long a, long b, long c, long d, long x, long s, long ac)
{
a += H(b, c, d) + x + ac;
a = ((int) a << s) | ((int) a >>> (32 - s));
a += b;
return a;
}
/**
* II.
*
* @param a the a
* @param b the b
* @param c the c
* @param d the d
* @param x the x
* @param s the s
* @param ac the ac
* @return the long
*/
private long II(long a, long b, long c, long d, long x, long s, long ac)
{
a += I(b, c, d) + x + ac;
a = ((int) a << s) | ((int) a >>> (32 - s));
a += b;
return a;
}
/**
* Md5 update.
*
* @param inbuf the inbuf
* @param inputLen the input len
*/
private void md5Update(byte[] inbuf, int inputLen)
{
int i, index, partLen;
byte[] block = new byte[64];
index = (int) (count[0] >>> 3) & 0x3F;
// /* Update number of bits */
if ((count[0] += (inputLen << 3)) < (inputLen << 3)) count[1]++;
count[1] += (inputLen >>> 29);
partLen = 64 - index;
// Transform as many times as possible.
if (inputLen >= partLen)
{
md5Memcpy(buffer, inbuf, index, 0, partLen);
md5Transform(buffer);
for (i = partLen; i + 63 < inputLen; i += 64)
{
md5Memcpy(block, inbuf, 0, i, 64);
md5Transform(block);
}
index = 0;
}
else
i = 0;
// /* Buffer remaining input */
md5Memcpy(buffer, inbuf, index, i, inputLen - i);
}
// public static void main(String[] args) {
// String method = "takeoutoverdue";
// System.out.println("method="+method);
// SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmssSSS");
// Date date = new Date();
// String ds = sdf.format(date);
// System.out.println("REQTIME="+ds);
// String sdeviceid = "dcdz1001";
// System.out.println("sdeviceid="+sdeviceid);
// String sign = toMD5(sdeviceid+method+ds);
// System.out.println(sign);
// System.out.println(Md5Password("18551672622","qwe123"));
// System.out.println(Md5ExpressPassword("935101"));
// System.out.println("A0317402FE1CA416DFF84F7B40B37D0A");
// System.out.println(Md5Password("10000000000","dd123456"));
// System.out.println(Md5Password("12312345625","123456"));
// }
/**
* Md5 final.
*/
private void md5Final()
{
byte[] bits = new byte[8];
int index, padLen;
// /* Save number of bits */
Encode(bits, count, 8);
// /* Pad out to 56 mod 64.
index = (int) (count[0] >>> 3) & 0x3f;
padLen = (index < 56) ? (56 - index) : (120 - index);
md5Update(PADDING, padLen);
// /* Append length (before padding) */
md5Update(bits, 8);
// /* Store state in digest */
Encode(digest, state, 16);
}
/**
* Md5 memcpy.
*
* @param output the output
* @param input the input
* @param outpos the outpos
* @param inpos the inpos
* @param len the len
*/
private void md5Memcpy(byte[] output, byte[] input, int outpos, int inpos, int len)
{
int i;
for (i = 0; i < len; i++)
output[outpos + i] = input[inpos + i];
}
/**
* Md5 transform.
*
* @param block the block
*/
private void md5Transform(byte block[])
{
long a = state[0], b = state[1], c = state[2], d = state[3];
long[] x = new long[16];
Decode(x, block, 64);
/* Round 1 */
a = FF(a, b, c, d, x[0], S11, 0xd76aa478L); /* 1 */
d = FF(d, a, b, c, x[1], S12, 0xe8c7b756L); /* 2 */
c = FF(c, d, a, b, x[2], S13, 0x242070dbL); /* 3 */
b = FF(b, c, d, a, x[3], S14, 0xc1bdceeeL); /* 4 */
a = FF(a, b, c, d, x[4], S11, 0xf57c0fafL); /* 5 */
d = FF(d, a, b, c, x[5], S12, 0x4787c62aL); /* 6 */
c = FF(c, d, a, b, x[6], S13, 0xa8304613L); /* 7 */
b = FF(b, c, d, a, x[7], S14, 0xfd469501L); /* 8 */
a = FF(a, b, c, d, x[8], S11, 0x698098d8L); /* 9 */
d = FF(d, a, b, c, x[9], S12, 0x8b44f7afL); /* 10 */
c = FF(c, d, a, b, x[10], S13, 0xffff5bb1L); /* 11 */
b = FF(b, c, d, a, x[11], S14, 0x895cd7beL); /* 12 */
a = FF(a, b, c, d, x[12], S11, 0x6b901122L); /* 13 */
d = FF(d, a, b, c, x[13], S12, 0xfd987193L); /* 14 */
c = FF(c, d, a, b, x[14], S13, 0xa679438eL); /* 15 */
b = FF(b, c, d, a, x[15], S14, 0x49b40821L); /* 16 */
/* Round 2 */
a = GG(a, b, c, d, x[1], S21, 0xf61e2562L); /* 17 */
d = GG(d, a, b, c, x[6], S22, 0xc040b340L); /* 18 */
c = GG(c, d, a, b, x[11], S23, 0x265e5a51L); /* 19 */
b = GG(b, c, d, a, x[0], S24, 0xe9b6c7aaL); /* 20 */
a = GG(a, b, c, d, x[5], S21, 0xd62f105dL); /* 21 */
d = GG(d, a, b, c, x[10], S22, 0x2441453L); /* 22 */
c = GG(c, d, a, b, x[15], S23, 0xd8a1e681L); /* 23 */
b = GG(b, c, d, a, x[4], S24, 0xe7d3fbc8L); /* 24 */
a = GG(a, b, c, d, x[9], S21, 0x21e1cde6L); /* 25 */
d = GG(d, a, b, c, x[14], S22, 0xc33707d6L); /* 26 */
c = GG(c, d, a, b, x[3], S23, 0xf4d50d87L); /* 27 */
b = GG(b, c, d, a, x[8], S24, 0x455a14edL); /* 28 */
a = GG(a, b, c, d, x[13], S21, 0xa9e3e905L); /* 29 */
d = GG(d, a, b, c, x[2], S22, 0xfcefa3f8L); /* 30 */
c = GG(c, d, a, b, x[7], S23, 0x676f02d9L); /* 31 */
b = GG(b, c, d, a, x[12], S24, 0x8d2a4c8aL); /* 32 */
/* Round 3 */
a = HH(a, b, c, d, x[5], S31, 0xfffa3942L); /* 33 */
d = HH(d, a, b, c, x[8], S32, 0x8771f681L); /* 34 */
c = HH(c, d, a, b, x[11], S33, 0x6d9d6122L); /* 35 */
b = HH(b, c, d, a, x[14], S34, 0xfde5380cL); /* 36 */
a = HH(a, b, c, d, x[1], S31, 0xa4beea44L); /* 37 */
d = HH(d, a, b, c, x[4], S32, 0x4bdecfa9L); /* 38 */
c = HH(c, d, a, b, x[7], S33, 0xf6bb4b60L); /* 39 */
b = HH(b, c, d, a, x[10], S34, 0xbebfbc70L); /* 40 */
a = HH(a, b, c, d, x[13], S31, 0x289b7ec6L); /* 41 */
d = HH(d, a, b, c, x[0], S32, 0xeaa127faL); /* 42 */
c = HH(c, d, a, b, x[3], S33, 0xd4ef3085L); /* 43 */
b = HH(b, c, d, a, x[6], S34, 0x4881d05L); /* 44 */
a = HH(a, b, c, d, x[9], S31, 0xd9d4d039L); /* 45 */
d = HH(d, a, b, c, x[12], S32, 0xe6db99e5L); /* 46 */
c = HH(c, d, a, b, x[15], S33, 0x1fa27cf8L); /* 47 */
b = HH(b, c, d, a, x[2], S34, 0xc4ac5665L); /* 48 */
/* Round 4 */
a = II(a, b, c, d, x[0], S41, 0xf4292244L); /* 49 */
d = II(d, a, b, c, x[7], S42, 0x432aff97L); /* 50 */
c = II(c, d, a, b, x[14], S43, 0xab9423a7L); /* 51 */
b = II(b, c, d, a, x[5], S44, 0xfc93a039L); /* 52 */
a = II(a, b, c, d, x[12], S41, 0x655b59c3L); /* 53 */
d = II(d, a, b, c, x[3], S42, 0x8f0ccc92L); /* 54 */
c = II(c, d, a, b, x[10], S43, 0xffeff47dL); /* 55 */
b = II(b, c, d, a, x[1], S44, 0x85845dd1L); /* 56 */
a = II(a, b, c, d, x[8], S41, 0x6fa87e4fL); /* 57 */
d = II(d, a, b, c, x[15], S42, 0xfe2ce6e0L); /* 58 */
c = II(c, d, a, b, x[6], S43, 0xa3014314L); /* 59 */
b = II(b, c, d, a, x[13], S44, 0x4e0811a1L); /* 60 */
a = II(a, b, c, d, x[4], S41, 0xf7537e82L); /* 61 */
d = II(d, a, b, c, x[11], S42, 0xbd3af235L); /* 62 */
c = II(c, d, a, b, x[2], S43, 0x2ad7d2bbL); /* 63 */
b = II(b, c, d, a, x[9], S44, 0xeb86d391L); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
/**
* Encode.
*
* @param output the output
* @param input the input
* @param len the len
*/
private void Encode(byte[] output, long[] input, int len)
{
int i, j;
for (i = 0, j = 0; j < len; i++, j += 4)
{
output[j] = (byte) (input[i] & 0xffL);
output[j + 1] = (byte) ((input[i] >>> 8) & 0xffL);
output[j + 2] = (byte) ((input[i] >>> 16) & 0xffL);
output[j + 3] = (byte) ((input[i] >>> 24) & 0xffL);
}
}
/**
* Decode.
*
* @param output the output
* @param input the input
* @param len the len
*/
private void Decode(long[] output, byte[] input, int len)
{
int i, j;
for (i = 0, j = 0; j < len; i++, j += 4)
output[i] = b2iu(input[j]) | (b2iu(input[j + 1]) << 8) | (b2iu(input[j + 2]) << 16)
| (b2iu(input[j + 3]) << 24);
return;
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import java.util.Comparator;
//比较器类
public class MapKeyComparator implements Comparator<String> {
public int compare(String str1, String str2) {
return str1.compareTo(str2);
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import java.net.URLEncoder;
import java.util.*;
import java.util.Map.Entry;
public class MapUtil {
/**
* 使用 Map按key进行排序
* @param map
* @return
*/
public static Map<String, String> sortMapByKey(Map<String, String> map) {
if (map == null || map.isEmpty()) {
return null;
}
Map<String, String> sortMap = new TreeMap<String, String>(new MapKeyComparator());
sortMap.putAll(map);
return sortMap;
}
/**
* 将map内的数据按key的顺序排序并拼接字符串
* @param paraMap
* @param urlencode
* @return
* @throws Exception
*/
public static String FormatBizQueryParaMap(Map<String, String> paraMap,
boolean urlencode) throws Exception {
String buff = "";
try {
List<Entry<String, String>> infoIds = new ArrayList<Entry<String, String>>(paraMap.entrySet());
Collections.sort(infoIds,
new Comparator<Entry<String, String>>() {
public int compare(Entry<String, String> o1,
Entry<String, String> o2) {
return (o1.getKey()).toString().compareTo(
o2.getKey());
}
});
for (int i = 0; i < infoIds.size(); i++) {
Entry<String, String> item = infoIds.get(i);
//System.out.println(item.getKey());
if (item.getKey() != "") {
String key = item.getKey();
String val = item.getValue();
if (urlencode) {
val = URLEncoder.encode(val, "utf-8");
}
buff += key + "=" + val + "&";
}
}
if (buff.isEmpty() == false) {
buff = buff.substring(0, buff.length() - 1);
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
return buff;
}
/**
* HashMap转成xml类型的String
* @param arr
* @return
*/
public static String ArrayToXml(HashMap<String, String> arr) {
String xml = "<xml>";
Iterator<Entry<String, String>> iter = arr.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
String key = entry.getKey();
String val = entry.getValue();
if (IsNumeric(val)) {
xml += "<" + key + ">" + val + "</" + key + ">";
} else
xml += "<" + key + "><![CDATA[" + val + "]]></" + key + ">";
}
xml += "</xml>";
return xml;
}
public static boolean IsNumeric(String str) {
if (str.matches("\\d *")) {
return true;
} else {
return false;
}
}
}
/*
* Copyright (c) 2015. xitai information and technology company
*/
package com.onsiteservice.miniapp.service.weixin.pay.util;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by peter on 15/5/12.
*/
public class ParseXmlUtil {
private static Logger logger = LoggerFactory.getLogger(ParseXmlUtil.class);
/**
* 解析得到相应键的值
*
* @param xml,key
* @return
* @throws Exception
*/
public static String parseXml2KeyValue(String xml, String key) throws Exception {
Document doc = null;
doc = DocumentHelper.parseText(xml); // 将字符串转为XML
Element rootElt = doc.getRootElement(); // 获取根节点
Element keyValue = rootElt.element(key);
String Value = keyValue.getText();
return Value;
}
/***
* 解析xml转换成Map
*
* @param xmlStr
* @return
* @throws Exception
*/
public static Map<String, String> parseXml2Map(String xml) throws Exception {
// 解析结果存储在HashMap
Map<String, String> map = new HashMap<String, String>();
Document reader = DocumentHelper.parseText(xml);
// 得到xml根元素
Element root = reader.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList) {
map.put(e.getName(), e.getText());
}
return map;
}
}
/*
* Copyright (c) 2015. xitai information and technology company
*/
package com.onsiteservice.miniapp.service.weixin.pay.util;
import java.net.InetAddress;
/**
* 生成UUID字符串
* @author wangyu
*
*/
public class UUIDGenerator {
/** The Constant IP. */
private static final int IP;
/** The Constant JVM. */
private static final int JVM = (int) (System.currentTimeMillis() >>> 8);
/** The counter. */
private static short counter = (short) 0;
static {
int ipadd;
try {
byte[] ip = InetAddress.getLocalHost().getAddress();
ipadd = ((((int) ip[0]) << 24) & 0xFF000000) | ((((int) ip[1]) << 16) & 0x00FF0000)
| ((((int) ip[2]) << 8) & 0x0000FF00) | (((int) ip[3]) & 0x000000FF);
} catch (Exception e) {
ipadd = 0;
}
IP = ipadd;
}
/**
* Gets the hi time.
*
* @return the hi time
*/
protected static short getHiTime() {
return (short) (System.currentTimeMillis() >>> 32);
}
/**
* Gets the lo time.
*
* @return the lo time
*/
protected static int getLoTime() {
return (int) System.currentTimeMillis();
}
/**
* Format.
*
* @param intval
* the intval
* @return the string
*/
protected static String format(int intval) {
String formatted = Integer.toHexString(intval);
StringBuffer buf = new StringBuffer("00000000");
buf.replace(8 - formatted.length(), 8, formatted);
return buf.toString();
}
/**
* Format.
*
* @param shortval
* the shortval
* @return the string
*/
protected static String format(short shortval) {
String formatted = Integer.toHexString(shortval);
StringBuffer buf = new StringBuffer("0000");
buf.replace(4 - formatted.length(), 4, formatted);
return buf.toString();
}
/**
* 产生一个32个字符长的UUID.
*
* @return the string
*/
public static synchronized String generate() {
return new StringBuffer(20).append(format(IP)).append(format(JVM)).append(format(getHiTime())).append(format(getLoTime()))
.append(format(counter++)).toString();
}
/**
* Generate8 hex.
*
* @return the string
*/
public static synchronized String generate8Hex() {
return format(getLoTime());
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import java.util.UUID;
/**
* @author wang jianguo
* @date 2020/10/20 15:14
* @email qizhi1a440@outlook.com
* @description:
*/
public class UUIDUtil {
public static String createUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import com.onsiteservice.miniapp.service.weixin.pay.config.WXPayConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key : data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
} catch (Exception ex) {
}
return output;
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, WXPayConstants.SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
// 参数值为空,则不参与签名
if (data.get(k).trim().length() > 0) {
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
}
sb.append("key=").append(key);
if (WXPayConstants.SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
} else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
} else {
throw new RuntimeException(String.format("Invalid sign_type: %s", signType));
}
}
public static void main(String[] args) {
HashMap<String, String> payParam = new HashMap<>();
payParam.put("appId", "wxba477f568e63e5bf");
payParam.put("timeStamp", "1554887738");
payParam.put("nonceStr", "ac1176ab6a0685af016a068803120006");
payParam.put("package", "prepay_id=wx10171538311638fe6baf44d41643020265");
payParam.put("signType", "MD5");
// payParam.put("appId", "wxd678efh567hg6787");
// payParam.put("timeStamp", "1490840662");
// payParam.put("nonceStr", "5K8264ILTKCH16CQ2502SI8ZNMTM67VS");
// payParam.put("package", "prepay_id=wx2017033010242291fcfe0db70013231072");
// payParam.put("signType", "MD5");
String paySign = null;
try {
paySign = WXPayUtil.generateSignature(payParam, "3346eeea6c667f056f948f9245b7b5eb");
System.out.println(paySign);
} catch (Exception e) {
e.printStackTrace();
}
payParam.put("paySign", paySign);
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
*
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 日志
*
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 获取当前时间戳,单位秒
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 获取当前时间戳,单位毫秒
*
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
}
package com.onsiteservice.miniapp.service.weixin.pay.util;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* 2018/7/3
*/
public final class WXPayXmlUtil {
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
}
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