parent
8726456126
commit
2aea8d4fbc
@ -0,0 +1,176 @@ |
||||
package com.cweb.controller; |
||||
|
||||
import com.github.pagehelper.PageHelper; |
||||
import com.github.pagehelper.PageInfo; |
||||
import com.hai.common.exception.ErrorCode; |
||||
import com.hai.common.exception.ErrorHelp; |
||||
import com.hai.common.exception.SysCode; |
||||
import com.hai.common.security.SessionObject; |
||||
import com.hai.common.security.UserCenter; |
||||
import com.hai.common.utils.DateUtil; |
||||
import com.hai.common.utils.IDGenerator; |
||||
import com.hai.common.utils.ResponseMsgUtil; |
||||
import com.hai.entity.HighChildOrder; |
||||
import com.hai.entity.HighCoupon; |
||||
import com.hai.entity.HighCouponCode; |
||||
import com.hai.entity.HighOrder; |
||||
import com.hai.model.HighCouponModel; |
||||
import com.hai.model.HighUserModel; |
||||
import com.hai.model.ResponseData; |
||||
import com.hai.model.UserInfoModel; |
||||
import com.hai.service.HighCouponCodeService; |
||||
import com.hai.service.HighCouponService; |
||||
import com.hai.service.HighOrderService; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import javax.annotation.Resource; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.math.BigDecimal; |
||||
import java.util.Date; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @Auther: 胡锐 |
||||
* @Description: |
||||
* @Date: 2021/3/26 23:08 |
||||
*/ |
||||
@Controller |
||||
@RequestMapping(value = "/highOrder") |
||||
@Api(value = "订单接口") |
||||
public class HighOrderController { |
||||
|
||||
private static Logger log = LoggerFactory.getLogger(HighMerchantStoreController.class); |
||||
|
||||
@Autowired |
||||
private UserCenter userCenter; |
||||
|
||||
@Resource |
||||
private HighOrderService highOrderService; |
||||
|
||||
@Resource |
||||
private HighCouponService highCouponService; |
||||
|
||||
@Resource |
||||
private HighCouponCodeService highCouponCodeService; |
||||
|
||||
@RequestMapping(value="/addOrder",method = RequestMethod.POST) |
||||
@ResponseBody |
||||
@ApiOperation(value = "增加订单") |
||||
public ResponseData addOrder(@RequestBody HighOrder highOrder, HttpServletRequest request) { |
||||
try { |
||||
|
||||
// 用户
|
||||
SessionObject sessionObject = userCenter.getSessionObject(request); |
||||
HighUserModel userInfoModel = (HighUserModel) sessionObject.getObject(); |
||||
|
||||
if (highOrder.getPayModel() == null |
||||
|| highOrder.getPayType() == null |
||||
|| highOrder.getHighChildOrderList() == null |
||||
|| highOrder.getHighChildOrderList().size() == 0 |
||||
) { |
||||
log.error("HighOrderController --> addOrder() error!", "参数错误"); |
||||
throw ErrorHelp.genException(SysCode.System, ErrorCode.REQ_PARAMS_ERROR, ""); |
||||
} |
||||
|
||||
BigDecimal totalPrice = new BigDecimal("0"); |
||||
for (HighChildOrder childOrder : highOrder.getHighChildOrderList()) { |
||||
if (childOrder.getGoodsType() == null || childOrder.getGoodsId() == null || childOrder.getSaleCount() == null) { |
||||
log.error("HighOrderController --> addOrder() error!", "参数错误"); |
||||
throw ErrorHelp.genException(SysCode.System, ErrorCode.REQ_PARAMS_ERROR, ""); |
||||
} |
||||
|
||||
// 商品类型 1:卡卷
|
||||
if (childOrder.getGoodsType() == 1) { |
||||
|
||||
// 查询卡卷信息
|
||||
HighCouponModel coupon = highCouponService.getCouponById(childOrder.getGoodsId()); |
||||
if (coupon == null) { |
||||
log.error("HighOrderController --> addOrder() error!", "未找到卡卷信息"); |
||||
throw ErrorHelp.genException(SysCode.System, ErrorCode.NOT_FOUND_COUPON, ""); |
||||
} |
||||
|
||||
if (highCouponCodeService.getStockCountByCoupon(coupon.getId()) <= 0) { |
||||
log.error("HighOrderController --> addOrder() error!", "卡卷库存数量不足"); |
||||
throw ErrorHelp.genException(SysCode.System, ErrorCode.COUPON_STOCK_INSUFFICIENT, ""); |
||||
} |
||||
|
||||
childOrder.setGoodsName(coupon.getCouponName()); |
||||
childOrder.setGoodsImg(coupon.getCouponImg()); |
||||
childOrder.setGoodsSpecName("默认"); |
||||
childOrder.setGoodsPrice(coupon.getDiscountPrice()); |
||||
childOrder.setTotalPrice(childOrder.getGoodsPrice().multiply(new BigDecimal(childOrder.getSaleCount().toString()))); |
||||
childOrder.setGiveawayType(false); // 是否是赠品 0:否 1:是
|
||||
childOrder.setChildOrdeStatus(1); // 1 待支付 2 已支付 3.已完成 4. 已退款 5.已取消
|
||||
childOrder.setPraiseStatus(0); |
||||
} |
||||
|
||||
// 累计订单价格
|
||||
totalPrice.add(childOrder.getTotalPrice()); |
||||
} |
||||
|
||||
highOrder.setOrderNo("HF" + DateUtil.date2String(new Date(),"yyyyMMddHHmmss") + IDGenerator.nextId(5)); |
||||
highOrder.setMemId(userInfoModel.getHighUser().getId()); |
||||
highOrder.setMemName(userInfoModel.getHighUser().getName()); |
||||
highOrder.setTotalPrice(totalPrice); |
||||
highOrder.setPayPrice(totalPrice); |
||||
highOrder.setCreateTime(new Date()); |
||||
highOrder.setOrderStatus(1); |
||||
|
||||
highOrderService.insertOrder(highOrder); |
||||
|
||||
return ResponseMsgUtil.success("操作成功"); |
||||
|
||||
} catch (Exception e) { |
||||
log.error("HighOrderController -> addOrder() error!",e); |
||||
return ResponseMsgUtil.exception(e); |
||||
} |
||||
} |
||||
|
||||
|
||||
@RequestMapping(value = "/getOrderById", method = RequestMethod.GET) |
||||
@ResponseBody |
||||
@ApiOperation(value = "根据id查询订单详情") |
||||
public ResponseData getOrderById(@RequestParam(name = "orderId", required = true) Long orderId) { |
||||
try { |
||||
|
||||
return ResponseMsgUtil.success(highOrderService.getOrderById(orderId)); |
||||
|
||||
} catch (Exception e) { |
||||
log.error("HighOrderController --> getOrderById() error!", e); |
||||
return ResponseMsgUtil.exception(e); |
||||
} |
||||
} |
||||
|
||||
|
||||
@RequestMapping(value = "/getUserOrderList", method = RequestMethod.GET) |
||||
@ResponseBody |
||||
@ApiOperation(value = "获取用户订单") |
||||
public ResponseData getUserOrderList(@RequestParam(name = "pageNum", required = true) Integer pageNum, |
||||
@RequestParam(name = "pageSize", required = true) Integer pageSize, |
||||
HttpServletRequest request) { |
||||
try { |
||||
|
||||
// 用户
|
||||
SessionObject sessionObject = userCenter.getSessionObject(request); |
||||
HighUserModel userInfoModel = (HighUserModel) sessionObject.getObject(); |
||||
|
||||
Map<String,Object> map = new HashMap<>(); |
||||
map.put("memId", userInfoModel.getHighUser().getId()); |
||||
|
||||
PageHelper.startPage(pageNum,pageSize); |
||||
return ResponseMsgUtil.success(new PageInfo<>(highCouponService.getCouponList(map))); |
||||
|
||||
} catch (Exception e) { |
||||
log.error("HighOrderController --> getUserOrderList() error!", e); |
||||
return ResponseMsgUtil.exception(e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,136 @@ |
||||
package com.cweb.controller.pay; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.cweb.config.SysConst; |
||||
import com.hai.common.exception.ErrorCode; |
||||
import com.hai.common.exception.ErrorHelp; |
||||
import com.hai.common.exception.SysCode; |
||||
import com.hai.common.pay.WechatPayUtil; |
||||
import com.hai.common.pay.entity.OrderType; |
||||
import com.hai.common.pay.entity.WeChatPayReqInfo; |
||||
import com.hai.common.pay.util.MD5Util; |
||||
import com.hai.common.security.SessionObject; |
||||
import com.hai.common.security.UserCenter; |
||||
import com.hai.common.utils.MathUtils; |
||||
import com.hai.common.utils.ResponseMsgUtil; |
||||
import com.hai.entity.HighOrder; |
||||
import com.hai.model.ResponseData; |
||||
import com.hai.model.UserInfoModel; |
||||
import com.hai.service.HighOrderService; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.web.bind.annotation.RequestBody; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RequestMethod; |
||||
import org.springframework.web.bind.annotation.ResponseBody; |
||||
|
||||
import javax.annotation.Resource; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.math.BigDecimal; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.SortedMap; |
||||
import java.util.concurrent.ThreadLocalRandom; |
||||
|
||||
/** |
||||
* |
||||
* @Title: |
||||
* @Description: 对订单的操作 |
||||
* @author: 魏真峰 |
||||
* @param: |
||||
* @return: |
||||
* @throws |
||||
*/ |
||||
@Controller |
||||
@RequestMapping("/order") |
||||
@Api(value = "对订单的操作") |
||||
public class OrderController { |
||||
|
||||
private static Logger log = LoggerFactory.getLogger(OrderController.class); |
||||
|
||||
@Autowired |
||||
private UserCenter userCenter; |
||||
|
||||
@Resource |
||||
private WechatPayUtil wechatPayUtil; |
||||
|
||||
@Resource |
||||
private HighOrderService highOrderService; |
||||
|
||||
/** |
||||
* |
||||
* @Title: orderToPay |
||||
* @Description: 订单支付发起支付 |
||||
* @author: 魏真峰 |
||||
* @param: [pageNum, pageSize] |
||||
* @return: com.shinwoten.haj.common.model.ResponseData |
||||
* @throws |
||||
*/ |
||||
@RequestMapping(value="/orderToPay",method = RequestMethod.POST) |
||||
@ResponseBody |
||||
@ApiOperation(value = "订单支付发起支付") |
||||
public ResponseData orderToPay(@RequestBody String reqBodyStr, |
||||
HttpServletRequest request) { |
||||
try { |
||||
if (StringUtils.isBlank(reqBodyStr)) { |
||||
log.error("orderToPay error!", "参数错误"); |
||||
throw ErrorHelp.genException(SysCode.System, ErrorCode.REQ_PARAMS_ERROR, ""); |
||||
} |
||||
JSONObject jsonObject = JSONObject.parseObject(reqBodyStr); |
||||
Long orderId = jsonObject.getLong("orderId"); |
||||
String openId = jsonObject.getString("openId"); // openId
|
||||
if ( orderId == null || StringUtils.isBlank(openId)) { |
||||
log.error("orderToPay error!", "参数错误"); |
||||
throw ErrorHelp.genException(SysCode.System, ErrorCode.REQ_PARAMS_ERROR, ""); |
||||
} |
||||
|
||||
HighOrder order = highOrderService.getOrderById(orderId); |
||||
if(order == null) { |
||||
log.error("OrderController --> orderToPay() ERROR", "未找到订单信息"); |
||||
throw ErrorHelp.genException(SysCode.System, ErrorCode.NOT_FOUND_ORDER, ""); |
||||
} |
||||
//校验订单状态 订单状态:1 待支付 2 已支付 3.已完成 4. 已退款 5.已取消
|
||||
if(order.getOrderStatus() != 1) { |
||||
log.error("OrderController --> orderToPay() ERROR", "订单不处于待支付状态"); |
||||
throw ErrorHelp.genException(SysCode.System, ErrorCode.ORDER_NO_STAY_PAY, ""); |
||||
} |
||||
|
||||
Map<String,Object> map = new HashMap<>(); |
||||
map.put("orderNo", order.getOrderNo()); |
||||
map.put("payPrice", order.getPayPrice()); |
||||
map.put("body","购买卡券"); |
||||
map.put("subject","购买卡券"); |
||||
|
||||
//微信支付
|
||||
String nonce_str = MD5Util.MD5Encode(String.valueOf(ThreadLocalRandom.current().nextInt(10000)), "UTF-8"); |
||||
int total_fee = MathUtils.objectConvertBigDecimal(map.get("payPrice")).multiply(new BigDecimal("100")).intValue(); |
||||
WeChatPayReqInfo weChatPayReqInfo = new WeChatPayReqInfo(); |
||||
weChatPayReqInfo.setAppid(SysConst.getSysConfig().getWxAppId()); //公众号id
|
||||
weChatPayReqInfo.setOpenid(openId); |
||||
weChatPayReqInfo.setMch_id(SysConst.getSysConfig().getWxMchId()); //商户号
|
||||
weChatPayReqInfo.setNonce_str(nonce_str); //随机字符串
|
||||
weChatPayReqInfo.setBody(map.get("body").toString()); //商品描述
|
||||
weChatPayReqInfo.setOut_trade_no(map.get("orderNo").toString()); //商户订单号
|
||||
weChatPayReqInfo.setTotal_fee(total_fee); //总金额
|
||||
weChatPayReqInfo.setSpbill_create_ip("139.159.177.244"); //终端ip
|
||||
weChatPayReqInfo.setNotify_url(SysConst.getSysConfig().getNotifyUrl()); //通知url
|
||||
weChatPayReqInfo.setTrade_type("JSAPI"); //交易类型
|
||||
// weChatPayReqInfo.setAttach(map.get("orderScene").toString()); //附加数据,区分订单类型
|
||||
Map<String,String> payMap = new HashMap<>(); |
||||
|
||||
payMap.put("app_id",SysConst.getSysConfig().getWxAppId()); |
||||
payMap.put("api_key",SysConst.getSysConfig().getWxApiKey()); |
||||
payMap.put("unified_order_url",SysConst.getSysConfig().getWxUnifiedOrderUrl()); |
||||
SortedMap<Object, Object> sortedMap = wechatPayUtil.goWechatPay(weChatPayReqInfo,payMap); |
||||
return ResponseMsgUtil.success(sortedMap); |
||||
} catch (Exception e) { |
||||
log.error("orderToPay error!", e); |
||||
return ResponseMsgUtil.exception(e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,146 @@ |
||||
package com.cweb.controller.pay; |
||||
|
||||
import com.cweb.config.SysConst; |
||||
import com.hai.common.pay.WechatPayUtil; |
||||
import com.hai.common.pay.util.IOUtil; |
||||
import com.hai.common.pay.util.SignatureUtil; |
||||
import com.hai.common.pay.util.XmlUtil; |
||||
import com.hai.service.pay.NotifyService; |
||||
import com.hai.service.pay.PayRecordService; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RequestMethod; |
||||
|
||||
import javax.annotation.Resource; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.BufferedOutputStream; |
||||
import java.util.SortedMap; |
||||
|
||||
@Controller |
||||
@RequestMapping(value = "/wechatpay") |
||||
@Api(value = "微信支付") |
||||
public class WechatPayController { |
||||
|
||||
private static Logger log = LoggerFactory.getLogger(WechatPayController.class); |
||||
|
||||
@Resource |
||||
private NotifyService notifyService; |
||||
@Resource |
||||
private PayRecordService payRecordService; |
||||
@Resource |
||||
private WechatPayUtil wechatPayUtil; |
||||
|
||||
@RequestMapping(value = "/notify", method = RequestMethod.POST) |
||||
@ApiOperation(value = "微信支付 -> 异步回调") |
||||
public void wechatNotify(HttpServletRequest request, HttpServletResponse response) { |
||||
try { |
||||
log.info("微信支付 -> 异步通知:处理开始"); |
||||
|
||||
String resXml = ""; // 反馈给微信服务器
|
||||
String notifyXml = null; // 微信支付系统发送的数据(<![CDATA[product_001]]>格式)
|
||||
notifyXml = IOUtil.inputStreamToString(request.getInputStream(), "UTF-8"); |
||||
|
||||
if (SignatureUtil.reCheckIsSignValidFromWeiXin(notifyXml, SysConst.getSysConfig().getWxApiKey(), "UTF-8")) { |
||||
log.info("微信支付系统发送的数据:" + notifyXml); |
||||
SortedMap<String, String> map = XmlUtil.parseXmlToTreeMap(notifyXml, "UTF-8"); |
||||
|
||||
resXml = notifyService.wechatNotify(map); |
||||
} else { |
||||
log.error("微信支付 -> 异步通知:验签失败"); |
||||
log.error("apiKey:" + SysConst.getSysConfig().getWxApiKey()); |
||||
log.error("返回信息:" + notifyXml); |
||||
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" |
||||
+ "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> "; |
||||
} |
||||
|
||||
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); |
||||
out.write(resXml.getBytes()); |
||||
out.flush(); |
||||
out.close(); |
||||
log.info("微信支付 -> 异步通知:处理完成"); |
||||
} catch (Exception e) { |
||||
log.error("WechatPayController --> wechatNotify() error!", e); |
||||
} |
||||
} |
||||
|
||||
/*@RequestMapping(value = "/testCreateOrder", method = RequestMethod.GET) |
||||
@ApiOperation(value = "微信测试 创建支付信息(发版时注释此接口)") |
||||
@ResponseBody |
||||
public SortedMap<Object, Object> testCreateOrder(@RequestParam(value = "subject", required = true) String subject, |
||||
@RequestParam(value = "orderNo", required = true) String orderNo, |
||||
@RequestParam(value = "totalPrice", required = true) Integer totalPrice, |
||||
HttpServletRequest httpRequest, HttpServletResponse httpResponse) { |
||||
try { |
||||
String nonce_str = MD5Util.MD5Encode(String.valueOf(ThreadLocalRandom.current().nextInt(10000)), "UTF-8"); |
||||
WeChatPayReqInfo weChatPayReqInfo = new WeChatPayReqInfo(); |
||||
weChatPayReqInfo.setAppid(wechatConfig.getApp_id()); //公众号id
|
||||
weChatPayReqInfo.setMch_id(wechatConfig.getMch_id()); //商户号
|
||||
weChatPayReqInfo.setNonce_str(nonce_str); //随机字符串
|
||||
weChatPayReqInfo.setBody(subject); //商品描述
|
||||
weChatPayReqInfo.setOut_trade_no(orderNo); //商户订单号
|
||||
weChatPayReqInfo.setTotal_fee(totalPrice); //总
|
||||
weChatPayReqInfo.setSpbill_create_ip("182.43.154.4"); //终端ip
|
||||
weChatPayReqInfo.setNotify_url(wechatConfig.getNotify_url()); //通知url
|
||||
//weChatPayReqInfo.setTrade_type("APP"); //交易类型
|
||||
weChatPayReqInfo.setTrade_type("JSAPI"); |
||||
//weChatPayReqInfo.setOpenid("");
|
||||
weChatPayReqInfo.setAttach("Test"); |
||||
|
||||
*//** 获取沙箱key start *//*
|
||||
String sandboxKey = GetSignKey(); |
||||
*//** 获取沙箱key end *//*
|
||||
String sign = SignatureUtil.createSign(weChatPayReqInfo, sandboxKey, "UTF-8"); // 测试 todo del
|
||||
weChatPayReqInfo.setSign(sign); |
||||
String unifiedXmL = XmlUtil.toSplitXml(weChatPayReqInfo); |
||||
|
||||
String unifiedOrderResultXmL = HttpReqUtil.HttpsDefaultExecute("POST", wechatConfig.getUnified_order_url(), null, unifiedXmL, null); |
||||
log.error("支付信息:" + unifiedOrderResultXmL); |
||||
// 签名校验
|
||||
SortedMap<Object,Object> sortedMap = null; |
||||
// 组装支付参数
|
||||
WechatCallBackInfo wechatCallBackInfo = XmlUtil.getObjectFromXML(unifiedOrderResultXmL, WechatCallBackInfo.class); |
||||
Long timeStamp = System.currentTimeMillis()/1000; |
||||
sortedMap = new TreeMap<>(); |
||||
sortedMap.put("appid",wechatConfig.getApp_id()); |
||||
sortedMap.put("partnerid",wechatConfig.getMch_id()); |
||||
sortedMap.put("prepayid",wechatCallBackInfo.getPrepay_id()); |
||||
sortedMap.put("noncestr",wechatCallBackInfo.getNonce_str()); |
||||
sortedMap.put("timestamp",timeStamp); |
||||
sortedMap.put("package","Sign=WXPay"); |
||||
String secondSign = SignatureUtil.createSign(sortedMap, wechatConfig.getApi_key(), "UTF-8"); |
||||
sortedMap.put("sign",secondSign); |
||||
|
||||
return sortedMap; |
||||
} catch (Exception e) { |
||||
log.error("AlipayTest -> createOrder:生成订单错误", e); |
||||
} |
||||
return null; |
||||
}*/ |
||||
|
||||
|
||||
/** |
||||
* @desc 获取沙箱key |
||||
*/ |
||||
/*public String GetSignKey() throws Exception { |
||||
String nonce_str = WXPayUtil.generateNonceStr(); //生成随机字符
|
||||
Map<String, String> param = new HashMap<>(); |
||||
param.put("mch_id", wechatConfig.getMch_id()); //需要真实商户号
|
||||
param.put("nonce_str", nonce_str); //随机字符
|
||||
String sign = WXPayUtil.generateSignature(param, wechatConfig.getApi_key(), WXPayConstants.SignType.MD5); //通过SDK生成签名其中API_KEY为商户对应的真实密钥
|
||||
param.put("sign", sign); |
||||
String xml = WXPayUtil.mapToXml(param); //将map转换为xml格式
|
||||
String url = "https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey"; //沙箱密钥获取api
|
||||
String signKey = HttpUtil.post(url, xml); |
||||
System.out.println("signkey+"+signKey); |
||||
Map<String, String> param1 = new HashMap<String, String>(); |
||||
param1 = WXPayUtil.xmlToMap(signKey.toString()); |
||||
String key = param1.get("sandbox_signkey"); |
||||
return key; |
||||
}*/ |
||||
|
||||
} |
@ -1,8 +1,11 @@ |
||||
# 微信配置 |
||||
wxAppId=wx8d49e2f83025229d |
||||
wxAppSecret=d8d6dcaef77d3b659258a01b5ddba5df |
||||
mch_id=1603942866 |
||||
api_key=HfkjWxPayHaiShengGuang0123456789 |
||||
wxApiKey=HfkjWxPayHaiShengGuang0123456789 |
||||
wxMchId=1603942866 |
||||
wxUnifiedOrderUrl=https://api.mch.weixin.qq.com/pay/unifiedorder |
||||
|
||||
notifyUrl=https://hsgcs.dctpay.com//crest/wechatpay/notify |
||||
|
||||
fileUrl=/home/project/hsg/filesystem |
||||
cmsPath=/home/project/hsg/filesystem/cmsPath |
||||
|
@ -0,0 +1,81 @@ |
||||
package com.hai.common.pay; |
||||
|
||||
import com.hai.common.pay.entity.WeChatPayReqInfo; |
||||
import com.hai.common.pay.entity.WechatCallBackInfo; |
||||
import com.hai.common.pay.util.HttpReqUtil; |
||||
import com.hai.common.pay.util.SignatureUtil; |
||||
import com.hai.common.pay.util.XmlUtil; |
||||
import com.hai.entity.HighPayRecord; |
||||
import com.hai.service.pay.PayRecordService; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.ResponseBody; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.math.BigDecimal; |
||||
import java.util.Map; |
||||
import java.util.SortedMap; |
||||
import java.util.TreeMap; |
||||
|
||||
@Component |
||||
public class WechatPayUtil { |
||||
|
||||
private static Logger log = LoggerFactory.getLogger(WechatPayUtil.class); |
||||
@Resource |
||||
private PayRecordService payRecordService; |
||||
|
||||
/** |
||||
* @throws |
||||
* @Title: goAlipay |
||||
* @Description: 微信支付请求体 |
||||
* @author: 魏真峰 |
||||
* @param: [orderId] |
||||
* @return: java.lang.String |
||||
*/ |
||||
@RequestMapping("/goWechatPay") |
||||
@ResponseBody |
||||
public SortedMap<Object,Object> goWechatPay(WeChatPayReqInfo weChatPayReqInfo, Map<String,String> map) throws Exception{ |
||||
log.info("微信支付 -> 组装支付参数:开始"); |
||||
|
||||
String sign = SignatureUtil.createSign(weChatPayReqInfo, map.get("api_key"), "UTF-8"); |
||||
weChatPayReqInfo.setSign(sign); |
||||
String unifiedXmL = XmlUtil.toSplitXml(weChatPayReqInfo); |
||||
|
||||
String unifiedOrderResultXmL = HttpReqUtil.HttpsDefaultExecute("POST", map.get("unified_order_url"), null, unifiedXmL, null); |
||||
// 签名校验
|
||||
SortedMap<Object,Object> sortedMap = null; |
||||
if (SignatureUtil.checkIsSignValidFromWeiXin(unifiedOrderResultXmL, map.get("api_key"), "UTF-8")) { |
||||
// 组装支付参数
|
||||
WechatCallBackInfo wechatCallBackInfo = XmlUtil.getObjectFromXML(unifiedOrderResultXmL, WechatCallBackInfo.class); |
||||
Long timeStamp = System.currentTimeMillis()/1000; |
||||
sortedMap = new TreeMap<>(); |
||||
sortedMap.put("appId",map.get("app_id")); |
||||
// sortedMap.put("partnerid",SysConst.getSysConfig().getMch_id());
|
||||
// sortedMap.put("prepayid",wechatCallBackInfo.getPrepay_id());
|
||||
sortedMap.put("nonceStr",wechatCallBackInfo.getNonce_str()); |
||||
sortedMap.put("timeStamp",timeStamp.toString()); |
||||
sortedMap.put("signType","MD5"); |
||||
sortedMap.put("package", "prepay_id=" + wechatCallBackInfo.getPrepay_id()); |
||||
String secondSign = SignatureUtil.createSign(sortedMap, map.get("api_key"), "UTF-8"); |
||||
sortedMap.put("sign",secondSign); |
||||
|
||||
// 将支付请求存入支付纪录
|
||||
HighPayRecord payRecord = new HighPayRecord(); |
||||
payRecord.setBodyInfo(unifiedXmL); |
||||
payRecord.setResType(1); |
||||
payRecord.setPayType(2); |
||||
payRecord.setPayMoney(new BigDecimal(weChatPayReqInfo.getTotal_fee()/100D)); |
||||
payRecordService.addPayRecord(payRecord); |
||||
|
||||
log.info("微信支付 -> 组装支付参数:完成"); |
||||
} else { |
||||
log.error("微信支付 -> 组装支付参数:支付信息错误"); |
||||
log.error("错误信息:" + unifiedOrderResultXmL); |
||||
} |
||||
|
||||
return sortedMap; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,151 @@ |
||||
package com.hai.common.pay.entity; |
||||
|
||||
/** |
||||
* @Description: 支付宝統一下单参数 |
||||
*/ |
||||
public class AliPayReqInfo { |
||||
private String timeout_express; //该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。 90m
|
||||
private String total_amount; //必填。订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000] 9.00
|
||||
private String seller_id; //收款支付宝用户ID。 如果该值为空,则默认为商户签约账号对应的支付宝用户ID 2088102147948060
|
||||
private String product_code; //销售产品码,商家和支付宝签约的产品码 QUICK_MSECURITY_PAY
|
||||
private String body; //对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body。 Iphone6 16G
|
||||
private String subject; //必填。商品的标题/交易标题/订单标题/订单关键字等。 大乐透
|
||||
private String out_trade_no; //必填。商户网站唯一订单号 70501111111S001111119
|
||||
private String time_expire; //绝对超时时间,格式为yyyy-MM-dd HH:mm。 2016-12-31 10:05
|
||||
private String goods_type; //商品主类型 :0-虚拟类商品,1-实物类商品 0
|
||||
private String promo_params; //优惠参数
|
||||
private String passback_params; //必填。公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。支付宝只会在同步返回(包括跳转回商户网站)和异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝。 merchantBizType%3d3C%26merchantBizNo%3d2016010101111
|
||||
private String enable_pay_channels; //可用渠道,用户只能在指定渠道范围内支付
|
||||
private String store_id; //商户门店编号 NJ_001
|
||||
private String specified_channel; //指定渠道,目前仅支持传入pcredit
|
||||
private String disable_pay_channels; //禁用渠道,用户不可用指定渠道支付
|
||||
private String business_params; //商户传入业务信息,具体值要和支付宝约定,应用于安全,营销等参数直传场景,格式为json格式
|
||||
|
||||
public String getTimeout_express() { |
||||
return timeout_express; |
||||
} |
||||
|
||||
public void setTimeout_express(String timeout_express) { |
||||
this.timeout_express = timeout_express; |
||||
} |
||||
|
||||
public String getTotal_amount() { |
||||
return total_amount; |
||||
} |
||||
|
||||
public void setTotal_amount(String total_amount) { |
||||
this.total_amount = total_amount; |
||||
} |
||||
|
||||
public String getSeller_id() { |
||||
return seller_id; |
||||
} |
||||
|
||||
public void setSeller_id(String seller_id) { |
||||
this.seller_id = seller_id; |
||||
} |
||||
|
||||
public String getProduct_code() { |
||||
return product_code; |
||||
} |
||||
|
||||
public void setProduct_code(String product_code) { |
||||
this.product_code = product_code; |
||||
} |
||||
|
||||
public String getBody() { |
||||
return body; |
||||
} |
||||
|
||||
public void setBody(String body) { |
||||
this.body = body; |
||||
} |
||||
|
||||
public String getSubject() { |
||||
return subject; |
||||
} |
||||
|
||||
public void setSubject(String subject) { |
||||
this.subject = subject; |
||||
} |
||||
|
||||
public String getOut_trade_no() { |
||||
return out_trade_no; |
||||
} |
||||
|
||||
public void setOut_trade_no(String out_trade_no) { |
||||
this.out_trade_no = out_trade_no; |
||||
} |
||||
|
||||
public String getTime_expire() { |
||||
return time_expire; |
||||
} |
||||
|
||||
public void setTime_expire(String time_expire) { |
||||
this.time_expire = time_expire; |
||||
} |
||||
|
||||
public String getGoods_type() { |
||||
return goods_type; |
||||
} |
||||
|
||||
public void setGoods_type(String goods_type) { |
||||
this.goods_type = goods_type; |
||||
} |
||||
|
||||
public String getPromo_params() { |
||||
return promo_params; |
||||
} |
||||
|
||||
public void setPromo_params(String promo_params) { |
||||
this.promo_params = promo_params; |
||||
} |
||||
|
||||
public String getPassback_params() { |
||||
return passback_params; |
||||
} |
||||
|
||||
public void setPassback_params(String passback_params) { |
||||
this.passback_params = passback_params; |
||||
} |
||||
|
||||
public String getEnable_pay_channels() { |
||||
return enable_pay_channels; |
||||
} |
||||
|
||||
public void setEnable_pay_channels(String enable_pay_channels) { |
||||
this.enable_pay_channels = enable_pay_channels; |
||||
} |
||||
|
||||
public String getStore_id() { |
||||
return store_id; |
||||
} |
||||
|
||||
public void setStore_id(String store_id) { |
||||
this.store_id = store_id; |
||||
} |
||||
|
||||
public String getSpecified_channel() { |
||||
return specified_channel; |
||||
} |
||||
|
||||
public void setSpecified_channel(String specified_channel) { |
||||
this.specified_channel = specified_channel; |
||||
} |
||||
|
||||
public String getDisable_pay_channels() { |
||||
return disable_pay_channels; |
||||
} |
||||
|
||||
public void setDisable_pay_channels(String disable_pay_channels) { |
||||
this.disable_pay_channels = disable_pay_channels; |
||||
} |
||||
|
||||
public String getBusiness_params() { |
||||
return business_params; |
||||
} |
||||
|
||||
public void setBusiness_params(String business_params) { |
||||
this.business_params = business_params; |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
package com.hai.common.pay.entity; |
||||
|
||||
public enum OrderType { |
||||
// 建议将支付频率高的模块放在前面
|
||||
GOODS_ORDER("GOODS_ORDER", "goodsOrderService", "购买商品"), |
||||
GOLD("GOLD", "", "金币充值"), |
||||
TEST("TEST", "testPayService", "支付测试") |
||||
; |
||||
|
||||
private String moduleCode; |
||||
private String service; |
||||
private String moduleName; |
||||
|
||||
private OrderType(String moduleCode, String service, String moduleName) { |
||||
this.moduleCode = moduleCode; |
||||
this.service = service; |
||||
this.moduleName = moduleName; |
||||
} |
||||
|
||||
public String getModuleCode() { |
||||
return moduleCode; |
||||
} |
||||
|
||||
private void setModuleCode(String moduleCode) { |
||||
this.moduleCode = moduleCode; |
||||
} |
||||
|
||||
public String getService() { |
||||
return service; |
||||
} |
||||
|
||||
private void setService(String service) { |
||||
this.service = service; |
||||
} |
||||
|
||||
public String getModuleName() { |
||||
return moduleName; |
||||
} |
||||
|
||||
private void setModuleName(String moduleName) { |
||||
this.moduleName = moduleName; |
||||
} |
||||
} |
@ -0,0 +1,205 @@ |
||||
package com.hai.common.pay.entity; |
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* @Description: 微信統一下单参数 |
||||
*/ |
||||
public class WeChatPayReqInfo implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -7642108447915413137L; |
||||
private String appid; // 公众号id 必填
|
||||
private String mch_id; // 商户号 必填
|
||||
private String nonce_str; // 随机字符串 必填
|
||||
private String sign; // 签名 必填
|
||||
private String device_info; // 设备号 可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
|
||||
private String body; // 商品描述 必填
|
||||
private String detail; // 商品详情
|
||||
private String attach; // 附加数据
|
||||
private String out_trade_no; // 商户订单号 必填
|
||||
private String fee_type; // 货币类型 默认为人民币CNY
|
||||
private Integer total_fee; // 总金额 传入int型的数据 必填
|
||||
private String spbill_create_ip; // 终端ip 必填
|
||||
private String time_start; // 交易起始时间 订单生成时间
|
||||
private String time_expire; // 交易结束时间 订单失效时间
|
||||
private String goods_tag; // 订单优惠标记
|
||||
private String notify_url; // 通知url 必填
|
||||
private String trade_type; // 交易类型 JSAPI,NATIVE,APP 必填
|
||||
private String product_id; //商品id trade_type=NATIVE时(即扫码支付),此参数必传
|
||||
private String limit_pay; // 指定支付方式 no_credit--可限制用户不能使用信用卡支付
|
||||
private String openid; // 用户标识(trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识)
|
||||
|
||||
private String scene_info; // 该字段用于统一下单时上报场景信息,目前支持上报实际门店信息 格式{"store_id":// "SZT10000", "store_name":"腾讯大厦腾大餐厅"}
|
||||
|
||||
public static long getSerialVersionUID() { |
||||
return serialVersionUID; |
||||
} |
||||
|
||||
public String getAppid() { |
||||
return appid; |
||||
} |
||||
|
||||
public void setAppid(String appid) { |
||||
this.appid = appid; |
||||
} |
||||
|
||||
public String getMch_id() { |
||||
return mch_id; |
||||
} |
||||
|
||||
public void setMch_id(String mch_id) { |
||||
this.mch_id = mch_id; |
||||
} |
||||
|
||||
public String getNonce_str() { |
||||
return nonce_str; |
||||
} |
||||
|
||||
public void setNonce_str(String nonce_str) { |
||||
this.nonce_str = nonce_str; |
||||
} |
||||
|
||||
public String getSign() { |
||||
return sign; |
||||
} |
||||
|
||||
public void setSign(String sign) { |
||||
this.sign = sign; |
||||
} |
||||
|
||||
public String getDevice_info() { |
||||
return device_info; |
||||
} |
||||
|
||||
public void setDevice_info(String device_info) { |
||||
this.device_info = device_info; |
||||
} |
||||
|
||||
public String getBody() { |
||||
return body; |
||||
} |
||||
|
||||
public void setBody(String body) { |
||||
this.body = body; |
||||
} |
||||
|
||||
public String getDetail() { |
||||
return detail; |
||||
} |
||||
|
||||
public void setDetail(String detail) { |
||||
this.detail = detail; |
||||
} |
||||
|
||||
public String getAttach() { |
||||
return attach; |
||||
} |
||||
|
||||
public void setAttach(String attach) { |
||||
this.attach = attach; |
||||
} |
||||
|
||||
public String getOut_trade_no() { |
||||
return out_trade_no; |
||||
} |
||||
|
||||
public void setOut_trade_no(String out_trade_no) { |
||||
this.out_trade_no = out_trade_no; |
||||
} |
||||
|
||||
public String getFee_type() { |
||||
return fee_type; |
||||
} |
||||
|
||||
public void setFee_type(String fee_type) { |
||||
this.fee_type = fee_type; |
||||
} |
||||
|
||||
public Integer getTotal_fee() { |
||||
return total_fee; |
||||
} |
||||
|
||||
public void setTotal_fee(Integer total_fee) { |
||||
this.total_fee = total_fee; |
||||
} |
||||
|
||||
public String getSpbill_create_ip() { |
||||
return spbill_create_ip; |
||||
} |
||||
|
||||
public void setSpbill_create_ip(String spbill_create_ip) { |
||||
this.spbill_create_ip = spbill_create_ip; |
||||
} |
||||
|
||||
public String getTime_start() { |
||||
return time_start; |
||||
} |
||||
|
||||
public void setTime_start(String time_start) { |
||||
this.time_start = time_start; |
||||
} |
||||
|
||||
public String getTime_expire() { |
||||
return time_expire; |
||||
} |
||||
|
||||
public void setTime_expire(String time_expire) { |
||||
this.time_expire = time_expire; |
||||
} |
||||
|
||||
public String getGoods_tag() { |
||||
return goods_tag; |
||||
} |
||||
|
||||
public void setGoods_tag(String goods_tag) { |
||||
this.goods_tag = goods_tag; |
||||
} |
||||
|
||||
public String getNotify_url() { |
||||
return notify_url; |
||||
} |
||||
|
||||
public void setNotify_url(String notify_url) { |
||||
this.notify_url = notify_url; |
||||
} |
||||
|
||||
public String getTrade_type() { |
||||
return trade_type; |
||||
} |
||||
|
||||
public void setTrade_type(String trade_type) { |
||||
this.trade_type = trade_type; |
||||
} |
||||
|
||||
public String getProduct_id() { |
||||
return product_id; |
||||
} |
||||
|
||||
public void setProduct_id(String product_id) { |
||||
this.product_id = product_id; |
||||
} |
||||
|
||||
public String getLimit_pay() { |
||||
return limit_pay; |
||||
} |
||||
|
||||
public void setLimit_pay(String limit_pay) { |
||||
this.limit_pay = limit_pay; |
||||
} |
||||
|
||||
public String getOpenid() { |
||||
return openid; |
||||
} |
||||
|
||||
public void setOpenid(String openid) { |
||||
this.openid = openid; |
||||
} |
||||
|
||||
public String getScene_info() { |
||||
return scene_info; |
||||
} |
||||
|
||||
public void setScene_info(String scene_info) { |
||||
this.scene_info = scene_info; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,154 @@ |
||||
package com.hai.common.pay.entity; |
||||
|
||||
/** |
||||
* 统一下单返回结果 |
||||
* @author phil |
||||
* @data 2017年6月27日 |
||||
* |
||||
*/ |
||||
public class WechatCallBackInfo { |
||||
|
||||
private static final long serialVersionUID = 9030465964635155064L; |
||||
private String appid; // 公众号id
|
||||
private String mch_id; // 商户号
|
||||
private String nonce_str; // 随机字符串
|
||||
private String sign; // 签名
|
||||
private String return_code; // 返回状态码
|
||||
private String return_msg; // 返回信息
|
||||
// 以下字段在return_code为SUCCESS的时候有返回(包括父类)
|
||||
private String device_info; // 设备号
|
||||
private String result_code; // 业务结果 SUCCESS/FAIL
|
||||
private String err_code; // 错误代码
|
||||
private String err_code_des; // 错误代码描述
|
||||
// 以下字段在return_code 和result_code都为SUCCESS的时候有返回
|
||||
private String trade_type; // 交易类型
|
||||
private String prepay_id; // 预支付交易会话标识,有效期为2小时
|
||||
private String code_url; // 二维码链接
|
||||
|
||||
public static long getSerialVersionUID() { |
||||
return serialVersionUID; |
||||
} |
||||
|
||||
public String getAppid() { |
||||
return appid; |
||||
} |
||||
|
||||
public void setAppid(String appid) { |
||||
this.appid = appid; |
||||
} |
||||
|
||||
public String getMch_id() { |
||||
return mch_id; |
||||
} |
||||
|
||||
public void setMch_id(String mch_id) { |
||||
this.mch_id = mch_id; |
||||
} |
||||
|
||||
public String getNonce_str() { |
||||
return nonce_str; |
||||
} |
||||
|
||||
public void setNonce_str(String nonce_str) { |
||||
this.nonce_str = nonce_str; |
||||
} |
||||
|
||||
public String getSign() { |
||||
return sign; |
||||
} |
||||
|
||||
public void setSign(String sign) { |
||||
this.sign = sign; |
||||
} |
||||
|
||||
public String getReturn_code() { |
||||
return return_code; |
||||
} |
||||
|
||||
public void setReturn_code(String return_code) { |
||||
this.return_code = return_code; |
||||
} |
||||
|
||||
public String getReturn_msg() { |
||||
return return_msg; |
||||
} |
||||
|
||||
public void setReturn_msg(String return_msg) { |
||||
this.return_msg = return_msg; |
||||
} |
||||
|
||||
public String getDevice_info() { |
||||
return device_info; |
||||
} |
||||
|
||||
public void setDevice_info(String device_info) { |
||||
this.device_info = device_info; |
||||
} |
||||
|
||||
public String getResult_code() { |
||||
return result_code; |
||||
} |
||||
|
||||
public void setResult_code(String result_code) { |
||||
this.result_code = result_code; |
||||
} |
||||
|
||||
public String getErr_code() { |
||||
return err_code; |
||||
} |
||||
|
||||
public void setErr_code(String err_code) { |
||||
this.err_code = err_code; |
||||
} |
||||
|
||||
public String getErr_code_des() { |
||||
return err_code_des; |
||||
} |
||||
|
||||
public void setErr_code_des(String err_code_des) { |
||||
this.err_code_des = err_code_des; |
||||
} |
||||
|
||||
public String getTrade_type() { |
||||
return trade_type; |
||||
} |
||||
|
||||
public void setTrade_type(String trade_type) { |
||||
this.trade_type = trade_type; |
||||
} |
||||
|
||||
public String getPrepay_id() { |
||||
return prepay_id; |
||||
} |
||||
|
||||
public void setPrepay_id(String prepay_id) { |
||||
this.prepay_id = prepay_id; |
||||
} |
||||
|
||||
public String getCode_url() { |
||||
return code_url; |
||||
} |
||||
|
||||
public void setCode_url(String code_url) { |
||||
this.code_url = code_url; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "WechatCallBackInfo{" + |
||||
"appid='" + appid + '\'' + |
||||
", mch_id='" + mch_id + '\'' + |
||||
", nonce_str='" + nonce_str + '\'' + |
||||
", sign='" + sign + '\'' + |
||||
", return_code='" + return_code + '\'' + |
||||
", return_msg='" + return_msg + '\'' + |
||||
", device_info='" + device_info + '\'' + |
||||
", result_code='" + result_code + '\'' + |
||||
", err_code='" + err_code + '\'' + |
||||
", err_code_des='" + err_code_des + '\'' + |
||||
", trade_type='" + trade_type + '\'' + |
||||
", prepay_id='" + prepay_id + '\'' + |
||||
", code_url='" + code_url + '\'' + |
||||
'}'; |
||||
} |
||||
} |
@ -0,0 +1,151 @@ |
||||
package com.hai.common.pay.entity; |
||||
/** |
||||
* |
||||
* @Description: 微信支付异步回调参数 |
||||
*/ |
||||
public class WechatPayReturnParam { |
||||
private String appid; //是 String(32) wx8888888888888888 微信开放平台审核通过的应用APPID
|
||||
private String attach; //否 String(128) 123456 商家数据包,原样返回
|
||||
private String bank_type; //是 String(16) CMC 银行类型,采用字符串类型的银行标识,银行类型见银行列表
|
||||
private String cash_fee; //是 Int 100 现金支付金额订单现金支付金额,详见支付金额
|
||||
private String fee_type; //否 String(8) CNY 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
|
||||
private String is_subscribe; //是 String(1) Y 用户是否关注公众账号,Y-关注,N-未关注
|
||||
private String mch_id; //是 String(32) 1900000109 微信支付分配的商户号
|
||||
private String nonce_str; //是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位
|
||||
private String openid; //是 String(128) wxd930ea5d5a258f4f 用户在商户appid下的唯一标识
|
||||
private String out_trade_no; //是 String(32) 1212321211201407033568112322 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
|
||||
private String result_code; //是 String(16) SUCCESS SUCCESS/FAIL
|
||||
private String return_code; |
||||
private String time_end; //是 String(14) 20141030133525 支付完成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规
|
||||
private String total_fee; //是 Int 100 订单总金额,单位为分
|
||||
private String trade_type; //是 String(16) APP APP
|
||||
private String transaction_id; //微信支付订单号
|
||||
|
||||
public String getAppid() { |
||||
return appid; |
||||
} |
||||
|
||||
public void setAppid(String appid) { |
||||
this.appid = appid; |
||||
} |
||||
|
||||
public String getAttach() { |
||||
return attach; |
||||
} |
||||
|
||||
public void setAttach(String attach) { |
||||
this.attach = attach; |
||||
} |
||||
|
||||
public String getBank_type() { |
||||
return bank_type; |
||||
} |
||||
|
||||
public void setBank_type(String bank_type) { |
||||
this.bank_type = bank_type; |
||||
} |
||||
|
||||
public String getCash_fee() { |
||||
return cash_fee; |
||||
} |
||||
|
||||
public void setCash_fee(String cash_fee) { |
||||
this.cash_fee = cash_fee; |
||||
} |
||||
|
||||
public String getFee_type() { |
||||
return fee_type; |
||||
} |
||||
|
||||
public void setFee_type(String fee_type) { |
||||
this.fee_type = fee_type; |
||||
} |
||||
|
||||
public String getIs_subscribe() { |
||||
return is_subscribe; |
||||
} |
||||
|
||||
public void setIs_subscribe(String is_subscribe) { |
||||
this.is_subscribe = is_subscribe; |
||||
} |
||||
|
||||
public String getMch_id() { |
||||
return mch_id; |
||||
} |
||||
|
||||
public void setMch_id(String mch_id) { |
||||
this.mch_id = mch_id; |
||||
} |
||||
|
||||
public String getNonce_str() { |
||||
return nonce_str; |
||||
} |
||||
|
||||
public void setNonce_str(String nonce_str) { |
||||
this.nonce_str = nonce_str; |
||||
} |
||||
|
||||
public String getOpenid() { |
||||
return openid; |
||||
} |
||||
|
||||
public void setOpenid(String openid) { |
||||
this.openid = openid; |
||||
} |
||||
|
||||
public String getOut_trade_no() { |
||||
return out_trade_no; |
||||
} |
||||
|
||||
public void setOut_trade_no(String out_trade_no) { |
||||
this.out_trade_no = out_trade_no; |
||||
} |
||||
|
||||
public String getResult_code() { |
||||
return result_code; |
||||
} |
||||
|
||||
public void setResult_code(String result_code) { |
||||
this.result_code = result_code; |
||||
} |
||||
|
||||
public String getReturn_code() { |
||||
return return_code; |
||||
} |
||||
|
||||
public void setReturn_code(String return_code) { |
||||
this.return_code = return_code; |
||||
} |
||||
|
||||
public String getTime_end() { |
||||
return time_end; |
||||
} |
||||
|
||||
public void setTime_end(String time_end) { |
||||
this.time_end = time_end; |
||||
} |
||||
|
||||
public String getTotal_fee() { |
||||
return total_fee; |
||||
} |
||||
|
||||
public void setTotal_fee(String total_fee) { |
||||
this.total_fee = total_fee; |
||||
} |
||||
|
||||
public String getTrade_type() { |
||||
return trade_type; |
||||
} |
||||
|
||||
public void setTrade_type(String trade_type) { |
||||
this.trade_type = trade_type; |
||||
} |
||||
|
||||
public String getTransaction_id() { |
||||
return transaction_id; |
||||
} |
||||
|
||||
public void setTransaction_id(String transaction_id) { |
||||
this.transaction_id = transaction_id; |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
package com.hai.common.pay.entity; |
||||
|
||||
public class WechatReturn { |
||||
private String appid; |
||||
private String mch_id; |
||||
private String nonce_str; |
||||
private String prepay_id; |
||||
private String result_code; |
||||
private String return_code; |
||||
private String return_msg; |
||||
private String trade_type; |
||||
|
||||
public String getAppid() { |
||||
return appid; |
||||
} |
||||
|
||||
public void setAppid(String appid) { |
||||
this.appid = appid; |
||||
} |
||||
|
||||
public String getMch_id() { |
||||
return mch_id; |
||||
} |
||||
|
||||
public void setMch_id(String mch_id) { |
||||
this.mch_id = mch_id; |
||||
} |
||||
|
||||
public String getNonce_str() { |
||||
return nonce_str; |
||||
} |
||||
|
||||
public void setNonce_str(String nonce_str) { |
||||
this.nonce_str = nonce_str; |
||||
} |
||||
|
||||
public String getPrepay_id() { |
||||
return prepay_id; |
||||
} |
||||
|
||||
public void setPrepay_id(String prepay_id) { |
||||
this.prepay_id = prepay_id; |
||||
} |
||||
|
||||
public String getResult_code() { |
||||
return result_code; |
||||
} |
||||
|
||||
public void setResult_code(String result_code) { |
||||
this.result_code = result_code; |
||||
} |
||||
|
||||
public String getReturn_code() { |
||||
return return_code; |
||||
} |
||||
|
||||
public void setReturn_code(String return_code) { |
||||
this.return_code = return_code; |
||||
} |
||||
|
||||
public String getReturn_msg() { |
||||
return return_msg; |
||||
} |
||||
|
||||
public void setReturn_msg(String return_msg) { |
||||
this.return_msg = return_msg; |
||||
} |
||||
|
||||
public String getTrade_type() { |
||||
return trade_type; |
||||
} |
||||
|
||||
public void setTrade_type(String trade_type) { |
||||
this.trade_type = trade_type; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,329 @@ |
||||
package com.hai.common.pay.util; |
||||
|
||||
import org.apache.commons.lang3.StringUtils; |
||||
|
||||
import javax.imageio.ImageIO; |
||||
import javax.net.ssl.*; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.awt.*; |
||||
import java.awt.image.BufferedImage; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.OutputStream; |
||||
import java.net.HttpURLConnection; |
||||
import java.net.URL; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
import java.util.Set; |
||||
import java.util.TreeMap; |
||||
|
||||
/** |
||||
* Http连接工具类 |
||||
* |
||||
* @author phil |
||||
* @date 2017年7月2日 |
||||
* |
||||
*/ |
||||
public class HttpReqUtil { |
||||
|
||||
private static int DEFAULT_CONNTIME = 5000; |
||||
private static int DEFAULT_READTIME = 5000; |
||||
private static int DEFAULT_UPLOAD_READTIME = 10 * 1000; |
||||
public static String POST = "POST"; |
||||
public static String GET = "GET"; |
||||
/** |
||||
* http请求 |
||||
* |
||||
* @param method |
||||
* 请求方法GET/POST |
||||
* @param path |
||||
* 请求路径 |
||||
* @param timeout |
||||
* 连接超时时间 默认为5000 |
||||
* @param readTimeout |
||||
* 读取超时时间 默认为5000 |
||||
* @param data |
||||
* 数据 |
||||
* @return |
||||
*/ |
||||
private static String defaultConnection(String method, String path, int timeout, int readTimeout, String data, String encoding) |
||||
throws Exception { |
||||
String result = ""; |
||||
URL url = new URL(path); |
||||
if (url != null) { |
||||
HttpURLConnection conn = getConnection(method, url); |
||||
conn.setConnectTimeout(timeout == 0 ? DEFAULT_CONNTIME : timeout); |
||||
conn.setReadTimeout(readTimeout == 0 ? DEFAULT_READTIME : readTimeout); |
||||
if (data != null && !"".equals(data)) { |
||||
OutputStream output = conn.getOutputStream(); |
||||
output.write(data.getBytes("UTF-8")); |
||||
output.flush(); |
||||
output.close(); |
||||
} |
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
||||
InputStream input = conn.getInputStream(); |
||||
result = IOUtil.inputStreamToString(input, encoding); |
||||
input.close(); |
||||
conn.disconnect(); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 根据url的协议选择对应的请求方式 |
||||
* |
||||
* @param method |
||||
* 请求的方法 |
||||
* @return |
||||
* @throws IOException |
||||
*/ |
||||
private static HttpURLConnection getConnection(String method, URL url) throws IOException { |
||||
HttpURLConnection conn = null; |
||||
if ("https".equals(url.getProtocol())) { |
||||
SSLContext context = null; |
||||
try { |
||||
context = SSLContext.getInstance("SSL", "SunJSSE"); |
||||
context.init(new KeyManager[0], new TrustManager[] { new MyX509TrustManager() }, |
||||
new java.security.SecureRandom()); |
||||
} catch (Exception e) { |
||||
throw new IOException(e); |
||||
} |
||||
HttpsURLConnection connHttps = (HttpsURLConnection) url.openConnection(); |
||||
connHttps.setSSLSocketFactory(context.getSocketFactory()); |
||||
connHttps.setHostnameVerifier(new HostnameVerifier() { |
||||
@Override |
||||
public boolean verify(String arg0, SSLSession arg1) { |
||||
return true; |
||||
} |
||||
}); |
||||
conn = connHttps; |
||||
} else { |
||||
conn = (HttpURLConnection) url.openConnection(); |
||||
} |
||||
conn.setRequestMethod(method); |
||||
conn.setUseCaches(false); |
||||
conn.setDoInput(true); |
||||
conn.setDoOutput(true); |
||||
return conn; |
||||
} |
||||
|
||||
/** |
||||
* 设置参数 |
||||
* |
||||
* @param map |
||||
* 参数map |
||||
* @param path |
||||
* 需要赋值的path |
||||
* @param charset |
||||
* 编码格式 默认编码为utf-8(取消默认) |
||||
* @return 已经赋值好的url 只需要访问即可 |
||||
*/ |
||||
public static String setParmas(Map<String, String> map, String path, String charset) throws Exception { |
||||
String result = ""; |
||||
boolean hasParams = false; |
||||
if (path != null && !"".equals(path)) { |
||||
if (map != null && map.size() > 0) { |
||||
StringBuilder builder = new StringBuilder(); |
||||
Set<Entry<String, String>> params = map.entrySet(); |
||||
for (Entry<String, String> entry : params) { |
||||
String key = entry.getKey().trim(); |
||||
String value = entry.getValue().trim(); |
||||
if (hasParams) { |
||||
builder.append("&"); |
||||
} else { |
||||
hasParams = true; |
||||
} |
||||
if (charset != null && !"".equals(charset)) { |
||||
// builder.append(key).append("=").append(URLDecoder.(value,charset));
|
||||
builder.append(key).append("=").append(IOUtil.urlEncode(value, charset)); |
||||
} else { |
||||
builder.append(key).append("=").append(value); |
||||
} |
||||
} |
||||
result = builder.toString(); |
||||
} |
||||
} |
||||
return doUrlPath(path, result).toString(); |
||||
} |
||||
|
||||
/** |
||||
* 设置连接参数 |
||||
* |
||||
* @param path |
||||
* 路径 |
||||
* @return |
||||
*/ |
||||
private static URL doUrlPath(String path, String query) throws Exception { |
||||
URL url = new URL(path); |
||||
if (StringUtils.isEmpty(path)) { |
||||
return url; |
||||
} |
||||
if (StringUtils.isEmpty(url.getQuery())) { |
||||
if (path.endsWith("?")) { |
||||
path += query; |
||||
} else { |
||||
path = path + "?" + query; |
||||
} |
||||
} else { |
||||
if (path.endsWith("&")) { |
||||
path += query; |
||||
} else { |
||||
path = path + "&" + query; |
||||
} |
||||
} |
||||
return new URL(path); |
||||
} |
||||
|
||||
/** |
||||
* 默认的http请求执行方法,返回 |
||||
* |
||||
* @param method |
||||
* 请求的方法 POST/GET |
||||
* @param path |
||||
* 请求path 路径 |
||||
* @param map |
||||
* 请求参数集合 |
||||
* @param data |
||||
* 输入的数据 允许为空 |
||||
* @return |
||||
*/ |
||||
public static String HttpDefaultExecute(String method, String path, Map<String, String> map, String data, String encoding) { |
||||
String result = ""; |
||||
try { |
||||
String url = setParmas((TreeMap<String, String>) map, path, ""); |
||||
result = defaultConnection(method, url, DEFAULT_CONNTIME, DEFAULT_READTIME, data, encoding); |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 默认的https执行方法,返回 |
||||
* |
||||
* @param method |
||||
* 请求的方法 POST/GET |
||||
* @param path |
||||
* 请求path 路径 |
||||
* @param map |
||||
* 请求参数集合 |
||||
* @param data |
||||
* 输入的数据 允许为空 |
||||
* @return |
||||
*/ |
||||
public static String HttpsDefaultExecute(String method, String path, Map<String, String> map, String data, String encoding) { |
||||
String result = ""; |
||||
try { |
||||
String url = setParmas((TreeMap<String, String>) map, path, ""); |
||||
result = defaultConnection(method, url, DEFAULT_CONNTIME, DEFAULT_READTIME, data, encoding); |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 文件路径 |
||||
* |
||||
* @param mediaUrl |
||||
* url 例如 |
||||
* http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png
|
||||
* @return logo_white_ee663702.png |
||||
*/ |
||||
private static String getFileName(String mediaUrl) { |
||||
String result = mediaUrl.substring(mediaUrl.lastIndexOf("/") + 1, mediaUrl.length()); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 根据内容类型判断文件扩展名 |
||||
* |
||||
* @param ContentType |
||||
* 内容类型 |
||||
* @return |
||||
*/ |
||||
private static String getFileExt(String contentType) { |
||||
String fileExt = ""; |
||||
if (contentType == null) { |
||||
return null; |
||||
} |
||||
if (contentType.contains("image/jpeg")) { |
||||
fileExt = ".jpg"; |
||||
} else if (contentType.contains("audio/mpeg")) { |
||||
fileExt = ".mp3"; |
||||
} else if (contentType.contains("audio/amr")) { |
||||
fileExt = ".amr"; |
||||
} else if (contentType.contains("video/mp4")) { |
||||
fileExt = ".mp4"; |
||||
} else if (contentType.contains("video/mpeg4")) { |
||||
fileExt = ".mp4"; |
||||
} else if (contentType.contains("image/png")) { |
||||
fileExt = ".png"; |
||||
} |
||||
return fileExt; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
/** |
||||
* 改变图片大小、格式 |
||||
* |
||||
* @param is |
||||
* @param os |
||||
* @param size |
||||
* @param format |
||||
* @return |
||||
* @throws IOException |
||||
*/ |
||||
public static OutputStream resizeImage(InputStream inputStream, OutputStream outputStream, int size, String format) |
||||
throws IOException { |
||||
BufferedImage prevImage = ImageIO.read(inputStream); |
||||
double width = prevImage.getWidth(); |
||||
double height = prevImage.getHeight(); |
||||
double percent = size / width; |
||||
int newWidth = (int) (width * percent); |
||||
int newHeight = (int) (height * percent); |
||||
BufferedImage image = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_BGR); |
||||
Graphics graphics = image.createGraphics(); |
||||
graphics.drawImage(prevImage, 0, 0, newWidth, newHeight, null); |
||||
ImageIO.write(image, format, outputStream); |
||||
outputStream.flush(); |
||||
return outputStream; |
||||
} |
||||
|
||||
/** |
||||
* 获取客户端ip |
||||
* |
||||
* @param request |
||||
* @return |
||||
*/ |
||||
public static String getRemortIP(HttpServletRequest request) { |
||||
String ip = request.getHeader("x-forwarded-for"); |
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { |
||||
ip = request.getHeader("Proxy-Client-IP"); |
||||
} |
||||
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { |
||||
ip = request.getHeader("WL-Proxy-Client-IP"); |
||||
} |
||||
|
||||
// squid的squid.conf 的配制文件中forwarded_for项改为off时
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { |
||||
ip = request.getRemoteAddr(); |
||||
} |
||||
|
||||
// 多级反向代理
|
||||
if (ip != null && ip.indexOf(",") > 0 && ip.split(",").length > 1) { |
||||
ip = ip.split(",")[0]; |
||||
} |
||||
return ip; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} |
||||
|
@ -0,0 +1,73 @@ |
||||
package com.hai.common.pay.util; |
||||
|
||||
import org.apache.commons.io.IOUtils; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.net.URLEncoder; |
||||
|
||||
/** |
||||
* IO流工具类 |
||||
* @author 魏真峰 |
||||
*/ |
||||
public class IOUtil { |
||||
|
||||
/** |
||||
* 将输入流转换为字符串 |
||||
*待转换为字符串的输入流 |
||||
* @return 由输入流转换String的字符串 |
||||
* @throws IOException |
||||
*/ |
||||
public static String inputStreamToString(InputStream inputStream, String encoding) throws IOException { |
||||
if(StringUtils.isEmpty(encoding)) { |
||||
encoding = "UTF-8"; |
||||
} |
||||
return IOUtils.toString(inputStream, encoding); |
||||
} |
||||
|
||||
/** |
||||
* 将字符串转换为输入流 |
||||
* 待转换为输入流的字符串 |
||||
* @return |
||||
* @throws IOException |
||||
*/ |
||||
public static InputStream toInputStream(String inputStr, String encoding) throws IOException { |
||||
if (StringUtils.isEmpty(inputStr)) { |
||||
return null; |
||||
} |
||||
if(StringUtils.isEmpty(encoding)) { |
||||
encoding = "UTF-8"; |
||||
} |
||||
return IOUtils.toInputStream(inputStr, encoding); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 编码 |
||||
* |
||||
* @param source |
||||
* @param encode |
||||
* @return |
||||
*/ |
||||
public static String urlEncode(String source, String encode) { |
||||
String result = source; |
||||
try { |
||||
result = URLEncoder.encode(source, encode); |
||||
} catch (UnsupportedEncodingException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 将输入流转换字节数组 |
||||
* @return |
||||
* @throws IOException |
||||
*/ |
||||
public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException { |
||||
return IOUtils.toByteArray(inputStream); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,55 @@ |
||||
package com.hai.common.pay.util; |
||||
|
||||
import org.apache.commons.lang3.StringUtils; |
||||
|
||||
import java.security.MessageDigest; |
||||
|
||||
/** |
||||
* MD5加密工具类 |
||||
* |
||||
* @author phil |
||||
* @date 2017年7月2日 |
||||
* |
||||
*/ |
||||
public class MD5Util { |
||||
|
||||
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", |
||||
"e", "f" }; |
||||
|
||||
private static String byteArrayToHexString(byte b[]) { |
||||
StringBuffer buffer = new StringBuffer(); |
||||
for (int i = 0; i < b.length; i++) |
||||
buffer.append(byteToHexString(b[i])); |
||||
return buffer.toString(); |
||||
} |
||||
|
||||
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]; |
||||
} |
||||
|
||||
public static String MD5Encode(String origin, String charsetname) { |
||||
String resultString = null; |
||||
try { |
||||
resultString = new String(origin); |
||||
MessageDigest md = MessageDigest.getInstance("MD5"); |
||||
if(StringUtils.isEmpty(charsetname)) { |
||||
resultString = byteArrayToHexString(md.digest(resultString.getBytes("UTF-8"))); |
||||
} else { |
||||
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname))); |
||||
} |
||||
} catch (Exception exception) { |
||||
|
||||
} |
||||
return resultString; |
||||
} |
||||
|
||||
public static void main(String args[]) { |
||||
System.out.println(MD5Encode("ceshi", "gbk")); |
||||
System.out.println(MD5Encode("ceshi", "utf-8")); |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
package com.hai.common.pay.util; |
||||
|
||||
import javax.net.ssl.X509TrustManager; |
||||
import java.security.cert.CertificateException; |
||||
import java.security.cert.X509Certificate; |
||||
|
||||
/** |
||||
* 自定义信任管理器 |
||||
* |
||||
* @author phil |
||||
* @date 2017年7月2日 |
||||
*/ |
||||
class MyX509TrustManager implements X509TrustManager { |
||||
|
||||
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { |
||||
} |
||||
|
||||
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { |
||||
} |
||||
|
||||
public X509Certificate[] getAcceptedIssuers() { |
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1,358 @@ |
||||
package com.hai.common.pay.util; |
||||
|
||||
import com.shinwoten.train.common.pay.entity.WechatPayReturnParam; |
||||
import com.shinwoten.train.common.pay.entity.WechatReturn; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.dom4j.DocumentException; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.xml.sax.SAXException; |
||||
|
||||
import javax.xml.parsers.ParserConfigurationException; |
||||
import java.io.IOException; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.lang.reflect.Field; |
||||
import java.security.MessageDigest; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Map; |
||||
import java.util.SortedMap; |
||||
|
||||
/** |
||||
* |
||||
* @Title: |
||||
* @Description: 微信支付签名工具类 |
||||
* @author: 魏真峰 |
||||
* @param: |
||||
* @return: |
||||
* @throws |
||||
*/ |
||||
public class SignatureUtil { |
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(SignatureUtil.class); |
||||
|
||||
/** |
||||
* 将字节数组转换为十六进制字符串 |
||||
* @param byteArray |
||||
* @return |
||||
*/ |
||||
private static String byteToStr(byte[] byteArray) { |
||||
String strDigest = ""; |
||||
for (int i = 0; i < byteArray.length; i++) { |
||||
strDigest += byteToHexStr(byteArray[i]); |
||||
} |
||||
return strDigest; |
||||
} |
||||
|
||||
/** |
||||
* 将字节转换为十六进制字符串 |
||||
* |
||||
* @param mByte |
||||
* @return |
||||
*/ |
||||
private static String byteToHexStr(byte mByte) { |
||||
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; |
||||
char[] tempArr = new char[2]; |
||||
tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; |
||||
tempArr[1] = Digit[mByte & 0X0F]; |
||||
return new String(tempArr); |
||||
} |
||||
|
||||
/** |
||||
* 获取签名 |
||||
* |
||||
* @param o |
||||
* 待加密的对象 该处仅限于Class |
||||
* @return |
||||
*/ |
||||
public static String createSign(Object o, String apiKey, String encoding) { |
||||
String result = notSignParams(o, apiKey); |
||||
result = MD5Util.MD5Encode(result, encoding).toUpperCase(); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 签名算法 |
||||
* |
||||
* @param o |
||||
* 要参与签名的数据对象 |
||||
* @param apiKey |
||||
* API密匙 |
||||
* @return 签名 |
||||
* @throws IllegalAccessException |
||||
*/ |
||||
public static String notSignParams(Object o, String apiKey) { |
||||
ArrayList<String> list = new ArrayList<>(); |
||||
String result = ""; |
||||
try { |
||||
Class<?> cls = o.getClass(); |
||||
Field[] fields = cls.getDeclaredFields(); |
||||
list = getFieldList(fields, o); |
||||
Field[] superFields = cls.getSuperclass().getDeclaredFields(); // 获取父类的私有属性
|
||||
list.addAll(getFieldList(superFields, o)); |
||||
int size = list.size(); |
||||
String[] arrayToSort = list.toArray(new String[size]); |
||||
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); // 严格按字母表顺序排序
|
||||
StringBuilder sb = new StringBuilder(); |
||||
for (int i = 0; i < size; i++) { |
||||
sb.append(arrayToSort[i]); |
||||
} |
||||
result = sb.toString(); |
||||
if (apiKey != null && !"".equals(apiKey)) { |
||||
result += "key=" + apiKey; |
||||
} else { |
||||
result = result.substring(0, result.lastIndexOf("&")); |
||||
} |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 将字段集合方法转换 |
||||
* |
||||
* @param array |
||||
* @param object |
||||
* @return |
||||
* @throws IllegalArgumentException |
||||
* @throws IllegalAccessException |
||||
*/ |
||||
private static ArrayList<String> getFieldList(Field[] array, Object object) |
||||
throws IllegalArgumentException, IllegalAccessException { |
||||
ArrayList<String> list = new ArrayList<String>(); |
||||
for (Field f : array) { |
||||
f.setAccessible(true); |
||||
if (f.get(object) != null && f.get(object) != "" && !f.getName().equals("serialVersionUID") |
||||
&& !f.getName().equals("sign")) { |
||||
if (f.getName().equals("packageStr")) { |
||||
list.add("package" + "=" + f.get(object) + "&"); |
||||
} else { |
||||
list.add(f.getName() + "=" + f.get(object) + "&"); |
||||
} |
||||
} |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
/** |
||||
* 通过Map<String,Object>中的所有元素参与签名 |
||||
* |
||||
* @param map |
||||
* 待参与签名的map集合 |
||||
* @params apikey apikey中 如果为空则不参与签名,如果不为空则参与签名 |
||||
* @return |
||||
*/ |
||||
public static String createSign(Map<Object, Object> map, String apiKey, String characterEncoding) { |
||||
String result = notSignParams(map, apiKey); |
||||
result = MD5Util.MD5Encode(result, characterEncoding).toUpperCase(); |
||||
logger.debug("sign result {}", result); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 通过Map<SortedMap,Object>中的所有元素参与签名 |
||||
* |
||||
* @param map |
||||
* 待参与签名的map集合 |
||||
* @params apikey apikey中 如果为空则不参与签名,如果不为空则参与签名 |
||||
* @return |
||||
*/ |
||||
public static String createSign(SortedMap<Object, Object> map, String apiKey, String characterEncoding) { |
||||
String result = notSignParams(map, apiKey); |
||||
result = MD5Util.MD5Encode(result, characterEncoding).toUpperCase(); |
||||
logger.debug("sign result {}", result); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 通过Map<SortedMap,Object>中的所有元素参与签名 |
||||
* |
||||
* @param map |
||||
* 待参与签名的map集合 |
||||
* @params apikey apikey中 如果为空则不参与签名,如果不为空则参与签名 |
||||
* @return |
||||
*/ |
||||
public static String createSha1Sign(SortedMap<Object, Object> map, String apiKey, String characterEncoding) { |
||||
String result = notSignParams(map, apiKey); |
||||
MessageDigest md = null; |
||||
try { |
||||
md = MessageDigest.getInstance("SHA-1"); |
||||
byte[] digest = md.digest(result.getBytes(characterEncoding)); |
||||
result = byteToStr(digest); |
||||
} catch (NoSuchAlgorithmException e) { |
||||
e.printStackTrace(); |
||||
} catch (UnsupportedEncodingException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 返回未加密的字符串 |
||||
* |
||||
* @param params |
||||
* @param apiKey |
||||
* @return 待加密的字符串 |
||||
*/ |
||||
private static String notSignParams(SortedMap<Object, Object> params, String apiKey) { |
||||
StringBuffer buffer = new StringBuffer(); |
||||
for (Map.Entry<Object, Object> entry : params.entrySet()) { |
||||
if (!org.springframework.util.StringUtils.isEmpty(entry.getValue())) { |
||||
buffer.append(entry.getKey() + "=" + entry.getValue() + "&"); |
||||
} |
||||
} |
||||
buffer.append("key=" + apiKey); |
||||
return buffer.toString(); |
||||
} |
||||
|
||||
/** |
||||
* 返回未加密的字符串 |
||||
* |
||||
* @param params |
||||
* @param apiKey |
||||
* @return 待加密的字符串 |
||||
*/ |
||||
public static String notSignParams(Map<Object, Object> params, String apiKey) { |
||||
ArrayList<String> list = new ArrayList<>(); |
||||
for (Map.Entry<Object, Object> entry : params.entrySet()) { |
||||
if (entry.getValue() != "" && entry.getValue() != null) { |
||||
list.add(entry.getKey() + "=" + entry.getValue() + "&"); |
||||
} |
||||
} |
||||
int size = list.size(); |
||||
String[] arrayToSort = list.toArray(new String[size]); |
||||
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); |
||||
StringBuilder sb = new StringBuilder(); |
||||
for (int i = 0; i < size; i++) { |
||||
sb.append(arrayToSort[i]); |
||||
} |
||||
String result = sb.toString(); |
||||
if (apiKey != null && !"".equals(apiKey)) { |
||||
result += "key=" + apiKey; |
||||
} else { |
||||
result = result.substring(0, result.lastIndexOf("&")); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 从API返回的XML数据里面重新计算一次签名 |
||||
* |
||||
* @param responseString |
||||
* API返回的XML数据 |
||||
* @param apiKey |
||||
* Key |
||||
* @return 新的签名 |
||||
* @throws ParserConfigurationException |
||||
* @throws IOException |
||||
* @throws SAXException |
||||
*/ |
||||
public static String reCreateSign(String responseString, String apiKey, String encoding) |
||||
throws IOException, SAXException, ParserConfigurationException { |
||||
Map<String, Object> map = XmlUtil.parseXmlToMap(responseString, encoding); |
||||
// 清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名
|
||||
map.replace("sign",""); |
||||
// 将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
|
||||
return createSign(map, apiKey, encoding); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 检验API返回的数据里面的签名是否合法,规则是:按参数名称a-z排序,遇到空值的参数不参加签名 |
||||
* |
||||
* @param resultXml |
||||
* API返回的XML数据字符串 |
||||
* @param apiKey |
||||
* Key |
||||
* @return API签名是否合法 |
||||
* @throws ParserConfigurationException |
||||
* @throws IOException |
||||
* @throws SAXException |
||||
* @throws DocumentException |
||||
*/ |
||||
public static boolean checkIsSignValidFromWeiXin(String checktXml, String apiKey, String encoding) |
||||
throws ParserConfigurationException, IOException, SAXException, DocumentException { |
||||
SortedMap<String, String> map = XmlUtil.parseXmlToTreeMap(checktXml,encoding); |
||||
String signFromresultXml = (String) map.get("sign"); |
||||
WechatReturn wechatReturn = new WechatReturn(); |
||||
wechatReturn.setAppid((String) map.get("appid")); |
||||
wechatReturn.setMch_id((String) map.get("mch_id")); |
||||
wechatReturn.setNonce_str((String) map.get("nonce_str")); |
||||
wechatReturn.setPrepay_id((String) map.get("prepay_id")); |
||||
wechatReturn.setResult_code((String) map.get("result_code")); |
||||
wechatReturn.setReturn_code((String) map.get("return_code")); |
||||
wechatReturn.setReturn_msg((String) map.get("return_msg")); |
||||
wechatReturn.setTrade_type((String) map.get("trade_type")); |
||||
if (StringUtils.isEmpty(signFromresultXml)) { |
||||
logger.debug("API返回的数据签名数据不存在"); |
||||
return false; |
||||
} |
||||
if(!("SUCCESS".equals(map.get("return_code"))) || !("SUCCESS".equals(map.get("result_code")))){ |
||||
logger.debug("返回代码不成功!"); |
||||
return false; |
||||
} |
||||
logger.debug("服务器回包里面的签名{}", signFromresultXml); |
||||
// 清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名
|
||||
// 将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
|
||||
String signForAPIResponse = createSign(wechatReturn, apiKey, encoding); |
||||
if (!signForAPIResponse.equals(signFromresultXml)) { |
||||
// 签名验不过,表示这个API返回的数据有可能已经被篡改了
|
||||
logger.debug("API返回的数据签名验证不通过"); |
||||
return false; |
||||
} |
||||
logger.debug("API返回的数据签名验证通过"); |
||||
return true; |
||||
} |
||||
/** |
||||
* |
||||
* @Title: reCheckIsSignValidFromWeiXin |
||||
* @Description: 微信支付异步回调,检验签名是否正确 |
||||
* @author: 魏真峰 |
||||
* @param: [checktXml, apiKey, encoding] |
||||
* @return: boolean |
||||
* @throws |
||||
*/ |
||||
public static boolean reCheckIsSignValidFromWeiXin(String checktXml, String apiKey, String encoding) |
||||
throws ParserConfigurationException, IOException, SAXException, DocumentException { |
||||
SortedMap<String, String> map = XmlUtil.parseXmlToTreeMap(checktXml,encoding); |
||||
String signFromresultXml = (String) map.get("sign"); |
||||
WechatPayReturnParam wechatPayReturnParam = new WechatPayReturnParam(); |
||||
wechatPayReturnParam.setAppid((String) map.get("appid")); |
||||
wechatPayReturnParam.setAttach((String) map.get("attach")); |
||||
wechatPayReturnParam.setBank_type((String) map.get("bank_type")); |
||||
wechatPayReturnParam.setCash_fee((String) map.get("cash_fee")); |
||||
wechatPayReturnParam.setFee_type((String) map.get("fee_type")); |
||||
wechatPayReturnParam.setIs_subscribe((String) map.get("is_subscribe")); |
||||
wechatPayReturnParam.setMch_id((String) map.get("mch_id")); |
||||
wechatPayReturnParam.setNonce_str((String) map.get("nonce_str")); |
||||
wechatPayReturnParam.setOpenid((String) map.get("openid")); |
||||
wechatPayReturnParam.setOut_trade_no((String) map.get("out_trade_no")); |
||||
wechatPayReturnParam.setResult_code((String) map.get("result_code")); |
||||
wechatPayReturnParam.setReturn_code((String) map.get("return_code")); |
||||
wechatPayReturnParam.setTime_end((String) map.get("time_end")); |
||||
wechatPayReturnParam.setTotal_fee((String) map.get("total_fee")); |
||||
wechatPayReturnParam.setTrade_type((String) map.get("trade_type")); |
||||
wechatPayReturnParam.setTransaction_id((String) map.get("transaction_id")); |
||||
if (StringUtils.isEmpty(signFromresultXml)) { |
||||
logger.debug("API返回的数据签名数据不存在"); |
||||
return false; |
||||
} |
||||
if(!("SUCCESS".equals(map.get("return_code"))) || !("SUCCESS".equals(map.get("result_code")))){ |
||||
logger.debug("返回代码不成功!"); |
||||
return false; |
||||
} |
||||
logger.debug("服务器回包里面的签名{}", signFromresultXml); |
||||
// 清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名
|
||||
// 将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
|
||||
String signForAPIResponse = createSign(wechatPayReturnParam, apiKey, encoding); |
||||
if (!signForAPIResponse.equals(signFromresultXml)) { |
||||
// 签名验不过,表示这个API返回的数据有可能已经被篡改了
|
||||
logger.debug("API返回的数据签名验证不通过"); |
||||
return false; |
||||
} |
||||
logger.debug("API返回的数据签名验证通过"); |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,274 @@ |
||||
package com.hai.common.pay.util; |
||||
|
||||
import com.thoughtworks.xstream.XStream; |
||||
import com.thoughtworks.xstream.core.util.QuickWriter; |
||||
import com.thoughtworks.xstream.io.HierarchicalStreamWriter; |
||||
import com.thoughtworks.xstream.io.xml.DomDriver; |
||||
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; |
||||
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder; |
||||
import com.thoughtworks.xstream.io.xml.XppDriver; |
||||
import org.dom4j.Document; |
||||
import org.dom4j.DocumentException; |
||||
import org.dom4j.Element; |
||||
import org.dom4j.io.SAXReader; |
||||
import org.xml.sax.SAXException; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.xml.parsers.DocumentBuilder; |
||||
import javax.xml.parsers.DocumentBuilderFactory; |
||||
import javax.xml.parsers.ParserConfigurationException; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.Writer; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.TreeMap; |
||||
|
||||
/** |
||||
* XML解析工具类 |
||||
* |
||||
* @author phil |
||||
* @date 2017年7月3日 |
||||
* |
||||
*/ |
||||
public class XmlUtil { |
||||
|
||||
/** |
||||
* 解析微信发来的请求(XML) xml示例 <xml> <ToUserName><![CDATA[toUser]]></ToUserName> |
||||
* <FromUserName><![CDATA[FromUser]]></FromUserName> |
||||
* <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> |
||||
* <Event><![CDATA[CLICK]]></Event> |
||||
* <EventKey><![CDATA[EVENTKEY]]></EventKey> </xml> |
||||
* |
||||
* @param request |
||||
* @return |
||||
* @throws Exception |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static Map<String, String> parseXmlToMap(HttpServletRequest request){ |
||||
Map<String, String> map = new HashMap<>(); |
||||
InputStream inputStream = null; |
||||
try { |
||||
// 从request中取得输入流
|
||||
inputStream = request.getInputStream(); |
||||
// 读取输入流
|
||||
SAXReader reader = new SAXReader(); |
||||
Document document = reader.read(inputStream); |
||||
// 得到xml根元素
|
||||
Element root = document.getRootElement(); |
||||
// 得到根元素的所有子节点
|
||||
List<Element> elementList = root.elements(); |
||||
// 遍历所有子节点
|
||||
for (Element e : elementList) { |
||||
map.put(e.getName(), e.getText()); |
||||
} |
||||
} catch (IOException | DocumentException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return map; |
||||
} |
||||
|
||||
/** |
||||
* 解析微信发来的请求(XML) xml示例 <xml> <ToUserName><![CDATA[toUser]]></ToUserName> |
||||
* <FromUserName><![CDATA[FromUser]]></FromUserName> |
||||
* <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> |
||||
* <Event><![CDATA[CLICK]]></Event> |
||||
* <EventKey><![CDATA[EVENTKEY]]></EventKey> </xml> |
||||
* |
||||
* @param request |
||||
* @return |
||||
* @throws Exception |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static Map<String, String> parseStreamToMap(InputStream inputStream) throws Exception { |
||||
Map<String, String> map = new HashMap<>(); |
||||
try { |
||||
// 读取输入流
|
||||
SAXReader reader = new SAXReader(); |
||||
Document document = reader.read(inputStream); |
||||
// 得到xml根元素
|
||||
Element root = document.getRootElement(); |
||||
// 得到根元素的所有子节点
|
||||
List<Element> elementList = root.elements(); |
||||
// 遍历所有子节点
|
||||
for (Element e : elementList) { |
||||
map.put(e.getName(), e.getText()); |
||||
} |
||||
} catch (DocumentException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return map; |
||||
} |
||||
|
||||
/** |
||||
* 使用dom4将xml文件中的数据转换成SortedMap<String,Object> |
||||
* |
||||
* @param xmlString |
||||
* xml格式的字符串 |
||||
* @throws ParserConfigurationException |
||||
* @throws IOException |
||||
* @throws SAXException |
||||
*/ |
||||
public static TreeMap<String, String> parseXmlToTreeMap(String xml, String encoding) |
||||
throws ParserConfigurationException, IOException, SAXException { |
||||
// 这里用Dom的方式解析回包的最主要目的是防止API新增回包字段
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
||||
DocumentBuilder builder = factory.newDocumentBuilder(); |
||||
InputStream is = IOUtil.toInputStream(xml, encoding); |
||||
org.w3c.dom.Document document = builder.parse(is); |
||||
// 获取到document里面的全部结点
|
||||
org.w3c.dom.NodeList allNodes = document.getFirstChild().getChildNodes(); |
||||
org.w3c.dom.Node node; |
||||
TreeMap<String, String> map = new TreeMap<>(); |
||||
int i = 0; |
||||
while (i < allNodes.getLength()) { |
||||
node = allNodes.item(i); |
||||
if (node instanceof org.w3c.dom.Element) { |
||||
map.put(node.getNodeName(), node.getTextContent()); |
||||
} |
||||
i++; |
||||
} |
||||
return map; |
||||
} |
||||
|
||||
/** |
||||
* 使用dom4将xml文件中的数据转换成Map<String,Object> |
||||
* |
||||
* @param xmlString xml格式的字符串 |
||||
* @throws ParserConfigurationException |
||||
* @throws IOException |
||||
* @throws SAXException |
||||
*/ |
||||
public static Map<String, Object> parseXmlToMap(String xml, String encoding) |
||||
throws ParserConfigurationException, IOException, SAXException { |
||||
// 这里用Dom的方式解析回包的最主要目的是防止API新增回包字段
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
||||
DocumentBuilder builder = factory.newDocumentBuilder(); |
||||
InputStream is = IOUtil.toInputStream(xml, encoding); |
||||
org.w3c.dom.Document document = builder.parse(is); |
||||
// 获取到document里面的全部结点
|
||||
org.w3c.dom.NodeList allNodes = document.getFirstChild().getChildNodes(); |
||||
org.w3c.dom.Node node; |
||||
Map<String, Object> map = new HashMap<>(); |
||||
int i = 0; |
||||
while (i < allNodes.getLength()) { |
||||
node = allNodes.item(i); |
||||
if (node instanceof org.w3c.dom.Element) { |
||||
map.put(node.getNodeName(), node.getTextContent()); |
||||
} |
||||
i++; |
||||
} |
||||
return map; |
||||
} |
||||
|
||||
/** |
||||
* 示例 <xml> <return_code><![CDATA[SUCCESS]]></return_code> |
||||
* <return_msg><![CDATA[OK]]></return_msg> |
||||
* <appid><![CDATA[wx2421b1c4370ec43b]]></appid> |
||||
* <mch_id><![CDATA[10000100]]></mch_id> |
||||
* <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str> |
||||
* <openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid> |
||||
* <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign> |
||||
* <result_code><![CDATA[SUCCESS]]></result_code> |
||||
* <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id> |
||||
* <trade_type><![CDATA[JSAPI]]></trade_type> </xml> |
||||
* |
||||
* 将xml数据(<![CDATA[SUCCESS]]>格式)映射到java对象中 |
||||
* |
||||
* @param xml |
||||
* 待转换的xml格式的数据 |
||||
* @param t |
||||
* 待转换为的java对象 |
||||
* @return |
||||
*/ |
||||
public static <T> T getObjectFromXML(String xml, Class<T> t) { |
||||
// 将从API返回的XML数据映射到Java对象
|
||||
XStream xstream = XStreamFactroy.init(true); |
||||
xstream.alias("xml", t); |
||||
xstream.ignoreUnknownElements();// 暂时忽略掉一些新增的字段
|
||||
// if(Objects.equals(xstream.fromXML(xml).getClass(), t.getClass())) {
|
||||
// return t.cast(xstream.fromXML(xml));
|
||||
// }
|
||||
// return null;
|
||||
return t.cast(xstream.fromXML(xml)); |
||||
} |
||||
|
||||
/** |
||||
* 将java对象转换为xml(<![CDATA[SUCCESS]]>格式) |
||||
* |
||||
* @param obj |
||||
* @return |
||||
*/ |
||||
public static String toXml(Object obj) { |
||||
String result = ""; |
||||
XStream xstream = XStreamFactroy.init(true); |
||||
xstream.alias("xml", obj.getClass()); |
||||
result = xstream.toXML(obj); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 将java对象转换为xml文件,并去除 _ 应用场景是 去除实体中有_划线的实体, 默认会有两个_,调用该方法则会去除一个 |
||||
* |
||||
* @param obj |
||||
* @return |
||||
*/ |
||||
public static String toSplitXml(Object obj) { |
||||
String result = ""; |
||||
XStream xstream = XStreamFactroy.initSplitLine(); |
||||
xstream.alias("xml", obj.getClass()); |
||||
result = xstream.toXML(obj); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* XStream工具类 |
||||
* @author phil |
||||
* |
||||
*/ |
||||
static class XStreamFactroy { |
||||
|
||||
private static final String START_CADA = "<![CDATA["; |
||||
private static final String END_CADA = "]]>"; |
||||
|
||||
/** |
||||
* 是否启用<![CDATA[]]> |
||||
* |
||||
* @param flag true 表示启用 false表示不启用 |
||||
* @return |
||||
*/ |
||||
static XStream init(boolean flag) { |
||||
XStream xstream = null; |
||||
if (flag) { |
||||
xstream = new XStream(new XppDriver() { |
||||
public HierarchicalStreamWriter createWriter(Writer out) { |
||||
return new PrettyPrintWriter(out) { |
||||
@Override |
||||
protected void writeText(QuickWriter writer, String text) { |
||||
if (!text.startsWith(START_CADA)) { |
||||
text = START_CADA + text + END_CADA; |
||||
} |
||||
writer.write(text); |
||||
} |
||||
}; |
||||
} |
||||
}); |
||||
} else { |
||||
xstream = new XStream(); |
||||
} |
||||
return xstream; |
||||
} |
||||
|
||||
/** |
||||
* 用于处理在实体对象中带有_的属性,如果用上述方法,会出现有两个__,导致结果不正确! 属性中有_的属性一定要有改方法 |
||||
* |
||||
* @return 返回xstream 对象 new DomDriver("UTF-8", new |
||||
* XmlFriendlyNameCoder("-_", "_") |
||||
*/ |
||||
public static XStream initSplitLine() { |
||||
XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_"))); |
||||
return xstream; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
package com.hai.common.pay.util.sdk; |
||||
|
||||
/** |
||||
* 域名管理,实现主备域名自动切换 |
||||
*/ |
||||
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 + |
||||
'}'; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,689 @@ |
||||
package com.hai.common.pay.util.sdk; |
||||
|
||||
import com.hai.common.pay.util.sdk.WXPayConstants.SignType; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
public class WXPay { |
||||
|
||||
private WXPayConfig config; |
||||
private SignType signType; |
||||
private boolean autoReport; |
||||
private boolean useSandbox; |
||||
private String notifyUrl; |
||||
private WXPayRequest wxPayRequest; |
||||
|
||||
public WXPay(final WXPayConfig config) throws Exception { |
||||
this(config, null, true, false); |
||||
} |
||||
|
||||
public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception { |
||||
this(config, null, autoReport, false); |
||||
} |
||||
|
||||
|
||||
public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception{ |
||||
this(config, null, autoReport, useSandbox); |
||||
} |
||||
|
||||
public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception { |
||||
this(config, notifyUrl, true, false); |
||||
} |
||||
|
||||
public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception { |
||||
this(config, notifyUrl, autoReport, false); |
||||
} |
||||
|
||||
public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception { |
||||
this.config = config; |
||||
this.notifyUrl = notifyUrl; |
||||
this.autoReport = autoReport; |
||||
this.useSandbox = useSandbox; |
||||
if (useSandbox) { |
||||
this.signType = SignType.MD5; // 沙箱环境
|
||||
} |
||||
else { |
||||
this.signType = SignType.HMACSHA256; |
||||
} |
||||
this.wxPayRequest = new WXPayRequest(config); |
||||
} |
||||
|
||||
private void checkWXPayConfig() throws Exception { |
||||
if (this.config == null) { |
||||
throw new Exception("config is null"); |
||||
} |
||||
if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) { |
||||
throw new Exception("appid in config is empty"); |
||||
} |
||||
if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) { |
||||
throw new Exception("appid in config is empty"); |
||||
} |
||||
if (this.config.getCertStream() == null) { |
||||
throw new Exception("cert stream in config is empty"); |
||||
} |
||||
if (this.config.getWXPayDomain() == null){ |
||||
throw new Exception("config.getWXPayDomain() is null"); |
||||
} |
||||
|
||||
if (this.config.getHttpConnectTimeoutMs() < 10) { |
||||
throw new Exception("http connect timeout is too small"); |
||||
} |
||||
if (this.config.getHttpReadTimeoutMs() < 10) { |
||||
throw new Exception("http read timeout is too small"); |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 向 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 (SignType.MD5.equals(this.signType)) { |
||||
reqData.put("sign_type", WXPayConstants.MD5); |
||||
} |
||||
else if (SignType.HMACSHA256.equals(this.signType)) { |
||||
reqData.put("sign_type", WXPayConstants.HMACSHA256); |
||||
} |
||||
reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType)); |
||||
return reqData; |
||||
} |
||||
|
||||
/** |
||||
* 判断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); |
||||
} |
||||
|
||||
/** |
||||
* 判断支付结果通知中的sign是否有效 |
||||
* |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return 签名是否有效 |
||||
* @throws Exception |
||||
*/ |
||||
public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception { |
||||
String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE); |
||||
SignType signType; |
||||
if (signTypeInData == null) { |
||||
signType = SignType.MD5; |
||||
} |
||||
else { |
||||
signTypeInData = signTypeInData.trim(); |
||||
if (signTypeInData.length() == 0) { |
||||
signType = SignType.MD5; |
||||
} |
||||
else if (WXPayConstants.MD5.equals(signTypeInData)) { |
||||
signType = SignType.MD5; |
||||
} |
||||
else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) { |
||||
signType = SignType.HMACSHA256; |
||||
} |
||||
else { |
||||
throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData)); |
||||
} |
||||
} |
||||
return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 不需要证书的请求 |
||||
* @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; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 需要证书的请求 |
||||
* @param urlSuffix String |
||||
* @param reqData 向wxpay post的请求数据 Map |
||||
* @param connectTimeoutMs 超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public String requestWithCert(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.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport); |
||||
return resp; |
||||
} |
||||
|
||||
/** |
||||
* 处理 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 Exception(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 Exception(String.format("Invalid sign value in XML: %s", xmlStr)); |
||||
} |
||||
} |
||||
else { |
||||
throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 作用:提交刷卡支付<br> |
||||
* 场景:刷卡支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> microPay(Map<String, String> reqData) throws Exception { |
||||
return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:提交刷卡支付<br> |
||||
* 场景:刷卡支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.MICROPAY_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
/** |
||||
* 提交刷卡支付,针对软POS,尽可能做成功 |
||||
* 内置重试机制,最多60s |
||||
* @param reqData |
||||
* @return |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> microPayWithPos(Map<String, String> reqData) throws Exception { |
||||
return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs()); |
||||
} |
||||
|
||||
/** |
||||
* 提交刷卡支付,针对软POS,尽可能做成功 |
||||
* 内置重试机制,最多60s |
||||
* @param reqData |
||||
* @param connectTimeoutMs |
||||
* @return |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> microPayWithPos(Map<String, String> reqData, int connectTimeoutMs) throws Exception { |
||||
int remainingTimeMs = 60*1000; |
||||
long startTimestampMs = 0; |
||||
Map<String, String> lastResult = null; |
||||
Exception lastException = null; |
||||
|
||||
while (true) { |
||||
startTimestampMs = WXPayUtil.getCurrentTimestampMs(); |
||||
int readTimeoutMs = remainingTimeMs - connectTimeoutMs; |
||||
if (readTimeoutMs > 1000) { |
||||
try { |
||||
lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs); |
||||
String returnCode = lastResult.get("return_code"); |
||||
if (returnCode.equals("SUCCESS")) { |
||||
String resultCode = lastResult.get("result_code"); |
||||
String errCode = lastResult.get("err_code"); |
||||
if (resultCode.equals("SUCCESS")) { |
||||
break; |
||||
} |
||||
else { |
||||
// 看错误码,若支付结果未知,则重试提交刷卡支付
|
||||
if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) { |
||||
remainingTimeMs = remainingTimeMs - (int)(WXPayUtil.getCurrentTimestampMs() - startTimestampMs); |
||||
if (remainingTimeMs <= 100) { |
||||
break; |
||||
} |
||||
else { |
||||
WXPayUtil.getLogger().info("microPayWithPos: try micropay again"); |
||||
if (remainingTimeMs > 5*1000) { |
||||
Thread.sleep(5*1000); |
||||
} |
||||
else { |
||||
Thread.sleep(1*1000); |
||||
} |
||||
continue; |
||||
} |
||||
} |
||||
else { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
break; |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
lastResult = null; |
||||
lastException = ex; |
||||
} |
||||
} |
||||
else { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (lastResult == null) { |
||||
throw lastException; |
||||
} |
||||
else { |
||||
return lastResult; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
* 作用:统一下单<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; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX; |
||||
} |
||||
if(this.notifyUrl != null) { |
||||
reqData.put("notify_url", this.notifyUrl); |
||||
} |
||||
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:查询订单<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception { |
||||
return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:查询订单<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 int |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_ORDERQUERY_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.ORDERQUERY_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:撤销订单<br> |
||||
* 场景:刷卡支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> reverse(Map<String, String> reqData) throws Exception { |
||||
return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:撤销订单<br> |
||||
* 场景:刷卡支付<br> |
||||
* 其他:需要证书 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.REVERSE_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:关闭订单<br> |
||||
* 场景:公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception { |
||||
return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:关闭订单<br> |
||||
* 场景:公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> closeOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.CLOSEORDER_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:申请退款<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> refund(Map<String, String> reqData) throws Exception { |
||||
return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:申请退款<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付<br> |
||||
* 其他:需要证书 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_REFUND_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.REFUND_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:退款查询<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception { |
||||
return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:退款查询<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.REFUNDQUERY_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception { |
||||
return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:对账单下载<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付<br> |
||||
* 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data, |
||||
* 其中return_code为`SUCCESS`,data为对账单数据。 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return 经过封装的API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.DOWNLOADBILL_URL_SUFFIX; |
||||
} |
||||
String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim(); |
||||
Map<String, String> ret; |
||||
// 出现错误,返回XML数据
|
||||
if (respStr.indexOf("<") == 0) { |
||||
ret = WXPayUtil.xmlToMap(respStr); |
||||
} |
||||
else { |
||||
// 正常返回csv数据
|
||||
ret = new HashMap<String, String>(); |
||||
ret.put("return_code", WXPayConstants.SUCCESS); |
||||
ret.put("return_msg", "ok"); |
||||
ret.put("data", respStr); |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:交易保障<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> report(Map<String, String> reqData) throws Exception { |
||||
return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:交易保障<br> |
||||
* 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_REPORT_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.REPORT_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return WXPayUtil.xmlToMap(respXml); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:转换短链接<br> |
||||
* 场景:刷卡支付、扫码支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception { |
||||
return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:转换短链接<br> |
||||
* 场景:刷卡支付、扫码支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.SHORTURL_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:授权码查询OPENID接口<br> |
||||
* 场景:刷卡支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception { |
||||
return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 作用:授权码查询OPENID接口<br> |
||||
* 场景:刷卡支付 |
||||
* @param reqData 向wxpay post的请求数据 |
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒 |
||||
* @param readTimeoutMs 读超时时间,单位是毫秒 |
||||
* @return API返回数据 |
||||
* @throws Exception |
||||
*/ |
||||
public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
||||
String url; |
||||
if (this.useSandbox) { |
||||
url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX; |
||||
} |
||||
else { |
||||
url = WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX; |
||||
} |
||||
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
||||
return this.processResponseXml(respXml); |
||||
} |
||||
|
||||
|
||||
} // end class
|
@ -0,0 +1,103 @@ |
||||
package com.hai.common.pay.util.sdk; |
||||
|
||||
import java.io.InputStream; |
||||
|
||||
public abstract class WXPayConfig { |
||||
|
||||
|
||||
|
||||
/** |
||||
* 获取 App ID |
||||
* |
||||
* @return App ID |
||||
*/ |
||||
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; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,59 @@ |
||||
package com.hai.common.pay.util.sdk; |
||||
|
||||
import org.apache.http.client.HttpClient; |
||||
|
||||
/** |
||||
* 常量 |
||||
*/ |
||||
public class WXPayConstants { |
||||
|
||||
public enum SignType { |
||||
MD5, HMACSHA256 |
||||
} |
||||
|
||||
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"; |
||||
|
||||
// sandbox
|
||||
public static final String SANDBOX_MICROPAY_URL_SUFFIX = "/sandboxnew/pay/micropay"; |
||||
public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder"; |
||||
public static final String SANDBOX_ORDERQUERY_URL_SUFFIX = "/sandboxnew/pay/orderquery"; |
||||
public static final String SANDBOX_REVERSE_URL_SUFFIX = "/sandboxnew/secapi/pay/reverse"; |
||||
public static final String SANDBOX_CLOSEORDER_URL_SUFFIX = "/sandboxnew/pay/closeorder"; |
||||
public static final String SANDBOX_REFUND_URL_SUFFIX = "/sandboxnew/secapi/pay/refund"; |
||||
public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX = "/sandboxnew/pay/refundquery"; |
||||
public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill"; |
||||
public static final String SANDBOX_REPORT_URL_SUFFIX = "/sandboxnew/payitil/report"; |
||||
public static final String SANDBOX_SHORTURL_URL_SUFFIX = "/sandboxnew/tools/shorturl"; |
||||
public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid"; |
||||
|
||||
} |
||||
|
@ -0,0 +1,265 @@ |
||||
package com.hai.common.pay.util.sdk; |
||||
|
||||
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 { |
||||
|
||||
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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default"; |
||||
// private static final String REPORT_URL = "http://127.0.0.1:5000/test";
|
||||
|
||||
|
||||
private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000; |
||||
private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000; |
||||
|
||||
private LinkedBlockingQueue<String> reportMsgQueue = null; |
||||
private WXPayConfig config; |
||||
private ExecutorService executorService; |
||||
|
||||
private volatile static WXPayReport INSTANCE; |
||||
|
||||
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; |
||||
} |
||||
|
||||
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(); |
||||
} |
||||
|
||||
/** |
||||
* 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"); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,258 @@ |
||||
package com.hai.common.pay.util.sdk; |
||||
|
||||
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.DefaultHostnameVerifier; |
||||
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; |
||||
|
||||
import static com.shinwoten.train.common.pay.util.sdk.WXPayConstants.USER_AGENT; |
||||
|
||||
public class WXPayRequest { |
||||
private WXPayConfig config; |
||||
public WXPayRequest(WXPayConfig config) throws Exception{ |
||||
|
||||
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 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", 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 Exception("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); |
||||
} |
||||
} |
@ -0,0 +1,295 @@ |
||||
package com.hai.common.pay.util.sdk; |
||||
|
||||
import com.hai.common.pay.util.sdk.WXPayConstants.SignType; |
||||
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, 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, 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, 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, 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, SignType.MD5); |
||||
} |
||||
|
||||
/** |
||||
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。 |
||||
* |
||||
* @param data 待签名数据 |
||||
* @param key API密钥 |
||||
* @param signType 签名方式 |
||||
* @return 签名 |
||||
*/ |
||||
public static String generateSignature(final Map<String, String> data, String key, 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 (SignType.MD5.equals(signType)) { |
||||
return MD5(sb.toString()).toUpperCase(); |
||||
} |
||||
else if (SignType.HMACSHA256.equals(signType)) { |
||||
return HMACSHA256(sb.toString(), key); |
||||
} |
||||
else { |
||||
throw new Exception(String.format("Invalid sign_type: %s", signType)); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 获取随机字符串 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(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,30 @@ |
||||
package com.hai.common.pay.util.sdk; |
||||
|
||||
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(); |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
package com.hai.common.utils; |
||||
|
||||
import java.util.concurrent.locks.Lock; |
||||
import java.util.concurrent.locks.ReentrantLock; |
||||
|
||||
public class IDGenerator { |
||||
|
||||
private static final long ONE_STEP = 10; |
||||
private static final Lock LOCK = new ReentrantLock(); |
||||
private static long lastTime = System.nanoTime(); |
||||
private static short lastCount = 0; |
||||
private static int count = 0; |
||||
/** |
||||
* 可传入指定长度的值 |
||||
* @param length |
||||
* @return |
||||
*/ |
||||
@SuppressWarnings("finally") |
||||
public static String nextId(int... length) |
||||
{ |
||||
LOCK.lock(); |
||||
try { |
||||
if (lastCount == ONE_STEP) { |
||||
boolean done = false; |
||||
while (!done) { |
||||
long now = System.nanoTime(); |
||||
if (now == lastTime) { |
||||
try { |
||||
Thread.currentThread(); |
||||
Thread.sleep(1); |
||||
} catch (InterruptedException e) { |
||||
} |
||||
continue; |
||||
} else { |
||||
lastTime = now; |
||||
lastCount = 0; |
||||
done = true; |
||||
} |
||||
} |
||||
} |
||||
count = lastCount++; |
||||
} |
||||
finally |
||||
{ |
||||
LOCK.unlock(); |
||||
String idStr = lastTime+""+String.format("%02d",count); |
||||
if(length.length > 0 && length[0] > 0){ |
||||
idStr = idStr.substring(idStr.length()-length[0]<0?0:idStr.length()-length[0], idStr.length()); |
||||
} |
||||
return idStr; |
||||
} |
||||
} |
||||
public static void main(String[] args) |
||||
{ |
||||
// for(int i=0;i<100;i++)
|
||||
// {
|
||||
// System.out.println(nextId());
|
||||
// }
|
||||
// nextId(20);
|
||||
System.out.println(System.currentTimeMillis()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,25 @@ |
||||
package com.hai.common.utils; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.math.BigInteger; |
||||
|
||||
public class MathUtils { |
||||
|
||||
public static BigDecimal objectConvertBigDecimal(Object value) { |
||||
BigDecimal ret = null; |
||||
if (value != null) { |
||||
if (value instanceof BigDecimal) { |
||||
ret = (BigDecimal) value; |
||||
} else if (value instanceof String) { |
||||
ret = new BigDecimal((String) value); |
||||
} else if (value instanceof BigInteger) { |
||||
ret = new BigDecimal((BigInteger) value); |
||||
} else if (value instanceof Number) { |
||||
ret = new BigDecimal(((Number) value).doubleValue()); |
||||
} else { |
||||
throw new ClassCastException("Not possible to coerce [" + value + "] from class " + value.getClass() + " into a BigDecimal."); |
||||
} |
||||
} |
||||
return ret; |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
package com.hai.service; |
||||
|
||||
import com.hai.entity.HighOrder; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @Auther: 胡锐 |
||||
* @Description: |
||||
* @Date: 2021/3/26 23:04 |
||||
*/ |
||||
public interface HighOrderService { |
||||
|
||||
/** |
||||
* @Author 胡锐 |
||||
* @Description 增加订单 |
||||
* @Date 2021/3/26 23:05 |
||||
**/ |
||||
void insertOrder(HighOrder highOrder); |
||||
|
||||
/** |
||||
* @Author 胡锐 |
||||
* @Description 修改订单 |
||||
* @Date 2021/3/26 23:06 |
||||
**/ |
||||
void updateOrder(HighOrder highOrder); |
||||
|
||||
/** |
||||
* @Author 胡锐 |
||||
* @Description 根据id查询 |
||||
* @Date 2021/3/26 23:06 |
||||
**/ |
||||
HighOrder getOrderById(Long id); |
||||
|
||||
/** |
||||
* @Author 胡锐 |
||||
* @Description 根据订单号查询 |
||||
* @Date 2021/3/27 0:38 |
||||
**/ |
||||
HighOrder getOrderByOrderNo(String orderNo); |
||||
|
||||
/** |
||||
* @Author 胡锐 |
||||
* @Description 查询订单列表 |
||||
* @Date 2021/3/26 23:06 |
||||
**/ |
||||
List<HighOrder> getOrderList(Map<String,Object> map); |
||||
|
||||
} |
@ -0,0 +1,95 @@ |
||||
package com.hai.service.impl; |
||||
|
||||
import com.hai.dao.HighChildOrderMapper; |
||||
import com.hai.dao.HighOrderMapper; |
||||
import com.hai.entity.HighChildOrder; |
||||
import com.hai.entity.HighChildOrderExample; |
||||
import com.hai.entity.HighOrder; |
||||
import com.hai.entity.HighOrderExample; |
||||
import com.hai.service.HighOrderService; |
||||
import org.apache.commons.collections4.MapUtils; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Propagation; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @Auther: 胡锐 |
||||
* @Description: |
||||
* @Date: 2021/3/26 23:06 |
||||
*/ |
||||
@Service("highOrderService") |
||||
public class HighOrderServiceImpl implements HighOrderService { |
||||
|
||||
@Resource |
||||
private HighOrderMapper highOrderMapper; |
||||
|
||||
@Resource |
||||
private HighChildOrderMapper highChildOrderMapper; |
||||
|
||||
@Override |
||||
@Transactional(propagation= Propagation.REQUIRES_NEW) |
||||
public void insertOrder(HighOrder highOrder) { |
||||
highOrderMapper.insert(highOrder); |
||||
|
||||
for (HighChildOrder childOrder : highOrder.getHighChildOrderList()) { |
||||
childOrder.setOrderId(highOrder.getId()); |
||||
highChildOrderMapper.insert(childOrder); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void updateOrder(HighOrder highOrder) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public HighOrder getOrderById(Long id) { |
||||
HighOrder order = highOrderMapper.selectByPrimaryKey(id); |
||||
if (order == null) { |
||||
return null; |
||||
} |
||||
|
||||
HighChildOrderExample example = new HighChildOrderExample(); |
||||
example.createCriteria().andOrderIdEqualTo(order.getId()); |
||||
order.setHighChildOrderList(highChildOrderMapper.selectByExample(example)); |
||||
|
||||
return order; |
||||
} |
||||
|
||||
@Override |
||||
public HighOrder getOrderByOrderNo(String orderNo) { |
||||
HighOrderExample example = new HighOrderExample(); |
||||
example.createCriteria().andOrderNoEqualTo(orderNo); |
||||
|
||||
List<HighOrder> list = highOrderMapper.selectByExample(example); |
||||
if (list != null && list.size() > 0) { |
||||
return getOrderById(list.get(0).getId()); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public List<HighOrder> getOrderList(Map<String, Object> map) { |
||||
HighOrderExample example = new HighOrderExample(); |
||||
HighOrderExample.Criteria criteria = example.createCriteria(); |
||||
|
||||
if (MapUtils.getLong(map, "memId") != null) { |
||||
criteria.andMemIdEqualTo(MapUtils.getLong(map, "memId")); |
||||
} |
||||
|
||||
if (MapUtils.getInteger(map, "status") != null) { |
||||
criteria.andOrderStatusEqualTo(MapUtils.getInteger(map, "status")); |
||||
} |
||||
|
||||
if (MapUtils.getString(map, "orderNo") != null) { |
||||
criteria.andOrderNoEqualTo(MapUtils.getString(map, "orderNo")); |
||||
} |
||||
|
||||
example.setOrderByClause("create_time desc"); |
||||
return highOrderMapper.selectByExample(example); |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
package com.hai.service.pay; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @Description 支付宝/微信 支付回调 |
||||
* @author gongjia |
||||
* @date 2020/3/27 15:30 |
||||
* @Copyright 2019 www.shinwoten.com Inc. All rights reserved. |
||||
*/ |
||||
public interface NotifyService { |
||||
|
||||
/** |
||||
* |
||||
* @Title alipayNotify |
||||
* @Description 支付宝 验签成功后业务调用 |
||||
* @author gongjia |
||||
* @param paramsMap 异步回调返回的参数 |
||||
*/ |
||||
String alipayNotify(Map<String, String> paramsMap) throws Exception; |
||||
|
||||
/** |
||||
* |
||||
* @Title alipayNotify |
||||
* @Description 微信 验签成功后业务调用 |
||||
* @author gongjia |
||||
* @param paramsMap 异步回调返回的参数 |
||||
*/ |
||||
String wechatNotify(Map<String, String> paramsMap) throws Exception; |
||||
} |
@ -0,0 +1,30 @@ |
||||
package com.hai.service.pay; |
||||
|
||||
import com.hai.entity.HighPayRecord; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @ClassName: PayRecordService |
||||
* @Description: 支付纪录 |
||||
* @author: gongjia |
||||
* @date: 2020/3/27 9:48 |
||||
* @Copyright: 2019 www.shinwoten.com Inc. All rights reserved. |
||||
*/ |
||||
public interface PayRecordService { |
||||
|
||||
/** |
||||
* |
||||
* @author gongjia |
||||
* @param paramsMap 支付纪录信息(支付宝或微信回调传回的信息) |
||||
* @param payType 支付方式:Alipay-支付宝 WechatPay-微信 |
||||
*/ |
||||
int addPayRecord(Map<String, String> paramsMap, String payType) throws Exception; |
||||
|
||||
/** |
||||
* @desc 插入支付纪录 |
||||
* @author gongjia |
||||
*/ |
||||
int addPayRecord(HighPayRecord payRecord) throws Exception; |
||||
|
||||
} |
@ -0,0 +1,21 @@ |
||||
package com.hai.service.pay; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @ClassName: PayService |
||||
* @Description: 支付完成 不同模块实际的业务处理 |
||||
* @author: gongjia |
||||
* @date: 2020/3/27 16:28 |
||||
* @Copyright: 2019 www.shinwoten.com Inc. All rights reserved. |
||||
*/ |
||||
public interface PayService { |
||||
|
||||
/** |
||||
* @desc 不同模块实际的业务处理 |
||||
* @param map 支付宝/微信 异步回调返回的参数 |
||||
* @param payType 支付方式:Alipay-支付宝 WechatPay-微信 |
||||
*/ |
||||
void paySuccess(Map<String, String> map, String payType) throws Exception; |
||||
|
||||
} |
@ -0,0 +1,39 @@ |
||||
package com.hai.service.pay.impl; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.hai.entity.HighOrder; |
||||
import com.hai.service.HighOrderService; |
||||
import com.hai.service.pay.PayService; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.math.BigDecimal; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @Auther: 胡锐 |
||||
* @Description: |
||||
* @Date: 2021/3/27 00:35 |
||||
*/ |
||||
@Service("goodsOrderService") |
||||
public class GoodsOrderServiceImpl implements PayService { |
||||
|
||||
@Resource |
||||
private HighOrderService highOrderService; |
||||
|
||||
@Override |
||||
public void paySuccess(Map<String, String> map, String payType) throws Exception { |
||||
if (payType.equals("Alipay")) { |
||||
// 支付宝支付 todo 暂未开发
|
||||
return; |
||||
} |
||||
if (payType.equals("WechatPay")) { |
||||
HighOrder order = highOrderService.getOrderByOrderNo(map.get("out_trade_no")); |
||||
if (order == null) { |
||||
|
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,121 @@ |
||||
package com.hai.service.pay.impl; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.hai.common.pay.entity.OrderType; |
||||
import com.hai.common.utils.SpringContextUtil; |
||||
import com.hai.service.pay.NotifyService; |
||||
import com.hai.service.pay.PayRecordService; |
||||
import com.hai.service.pay.PayService; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Propagation; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.Map; |
||||
|
||||
@Service(value = "notifyService") |
||||
public class NotifyServiceImpl implements NotifyService { |
||||
|
||||
private static Logger log = LoggerFactory.getLogger(NotifyServiceImpl.class); |
||||
@Resource |
||||
private PayRecordService payRecordService; |
||||
|
||||
@Override |
||||
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) |
||||
public String alipayNotify(Map<String, String> params) throws Exception { |
||||
String result = "failure"; |
||||
String trade_status = params.get("trade_status"); |
||||
if ("TRADE_SUCCESS".equals(trade_status)) { |
||||
// 交易支付成功 默认触发
|
||||
|
||||
String orderType = params.get("passback_params"); |
||||
PayService payService = getPayService(orderType); |
||||
if (payService != null) { |
||||
payService.paySuccess(params, "Alipay"); // 商户内部实际的交易业务处理
|
||||
log.info("支付宝异步通知 -> 业务处理完成"); |
||||
result = "success"; |
||||
} else { |
||||
log.info("支付宝异步通知 -> 业务处理:payService获取失败"); |
||||
} |
||||
|
||||
} else if ("TRADE_CLOSED".equals(trade_status)) { |
||||
// 未付款交易超时关闭,或支付完成后全额退款 默认触发
|
||||
|
||||
// todo 业务流程
|
||||
|
||||
log.info("支付宝异步通知 -> 未付款交易超时关闭,或支付完成后全额退款"); |
||||
result = "success"; |
||||
} else if ("WAIT_BUYER_PAY".equals(trade_status)) { |
||||
// 交易创建,等待买家付款 默认不触发
|
||||
|
||||
// todo 业务流程
|
||||
|
||||
log.info("支付宝异步通知 -> 交易创建,等待买家付款"); |
||||
result = "success"; |
||||
} else if ("TRADE_FINISHED".equals(trade_status)) { |
||||
// 交易结束,不可退款 默认不触发
|
||||
|
||||
// todo 业务流程
|
||||
|
||||
log.info("支付宝异步通知 -> 交易结束,不可退款"); |
||||
result = "success"; |
||||
} |
||||
|
||||
// 增加支付纪录(实际是支付宝回调纪录)
|
||||
payRecordService.addPayRecord(params, "Alipay"); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) |
||||
public String wechatNotify(Map<String, String> paramsMap) throws Exception { |
||||
String resXml = null; |
||||
|
||||
if ("SUCCESS".equals(paramsMap.get("return_code")) && "SUCCESS".equals(paramsMap.get("result_code"))) { |
||||
log.info("微信支付 -> 异步通知:支付成功,进入订单处理"); |
||||
String orderScene = paramsMap.get("attach"); |
||||
JSONObject object = JSONObject.parseObject(orderScene); |
||||
// 订单类型
|
||||
String orderType = object.getString("orderScene"); |
||||
PayService payService = getPayService(orderType); |
||||
if (payService != null) { |
||||
payService.paySuccess(paramsMap, "WechatPay"); // 商户内部实际的交易业务处理
|
||||
log.info("微信支付 -> 异步通知:订单处理完成"); |
||||
} else { |
||||
log.error("微信支付 -> 异步通知:业务处理,payService获取失败"); |
||||
} |
||||
|
||||
// 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了
|
||||
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" |
||||
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; |
||||
} else { |
||||
log.error("微信支付 -> 异步通知:支付失败,错误信息:" + paramsMap.get("err_code_des")); |
||||
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[" |
||||
+ paramsMap.get("err_code_des") + "]]></return_msg>" + "</xml> "; |
||||
} |
||||
|
||||
if ("SUCCESS".equals(paramsMap.get("return_code"))) { |
||||
// 增加支付纪录(实际是微信回调纪录)
|
||||
payRecordService.addPayRecord(paramsMap, "WechatPay"); |
||||
} |
||||
return resXml; |
||||
} |
||||
|
||||
/** |
||||
* @desc 根据回调回传参数 选择业务service |
||||
* @param orderType 订单类型(回传参数) |
||||
*/ |
||||
private PayService getPayService(String orderType) throws Exception{ |
||||
PayService payService = null; |
||||
for (OrderType item : OrderType.values()) { |
||||
if (item.getModuleCode().equals(orderType)) { |
||||
payService = (PayService) SpringContextUtil.getBean(item.getService()); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return payService; |
||||
} |
||||
} |
@ -0,0 +1,60 @@ |
||||
package com.hai.service.pay.impl; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.hai.common.utils.DateUtil; |
||||
import com.hai.dao.HighPayRecordMapper; |
||||
import com.hai.entity.HighPayRecord; |
||||
import com.hai.service.pay.PayRecordService; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.math.BigDecimal; |
||||
import java.math.RoundingMode; |
||||
import java.util.Date; |
||||
import java.util.Map; |
||||
|
||||
@Service(value = "payRecordService") |
||||
public class PayRecordServiceImpl implements PayRecordService { |
||||
|
||||
@Resource |
||||
private HighPayRecordMapper payRecordMapper; |
||||
|
||||
@Override |
||||
public int addPayRecord(Map<String, String> paramsMap, String payType) throws Exception { |
||||
HighPayRecord payRecord = new HighPayRecord(); |
||||
if ("Alipay".equals(payType)) { |
||||
|
||||
payRecord.setPaySerialNo(paramsMap.get("trade_no")); |
||||
payRecord.setResType(2); |
||||
payRecord.setPayType(1); |
||||
payRecord.setPayMoney(new BigDecimal(paramsMap.get("receipt_amount"))); |
||||
payRecord.setPayTime(DateUtil.StringToDate(paramsMap.get("gmt_payment"), "yyyy-MM-dd HH:mm:ss")); |
||||
payRecord.setBodyInfo(JSONObject.toJSONString(paramsMap)); |
||||
payRecord.setPayResult(paramsMap.get("trade_status")); |
||||
|
||||
} else if ("WechatPay".equals(payType)) { |
||||
|
||||
payRecord.setPaySerialNo(paramsMap.get("transaction_id")); |
||||
payRecord.setResType(2); |
||||
payRecord.setPayType(2); |
||||
payRecord.setPayMoney(new BigDecimal(paramsMap.get("cash_fee")).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)); |
||||
payRecord.setPayTime(DateUtil.StringToDate(paramsMap.get("time_end"), "yyyyMMddHHmmss")); |
||||
payRecord.setBodyInfo(JSONObject.toJSONString(paramsMap)); |
||||
payRecord.setPayResult(paramsMap.get("result_code")); |
||||
|
||||
} else { |
||||
return 0; |
||||
} |
||||
|
||||
payRecord.setCreateTime(new Date()); |
||||
return payRecordMapper.insertSelective(payRecord); |
||||
} |
||||
|
||||
@Override |
||||
public int addPayRecord(HighPayRecord payRecord) throws Exception { |
||||
|
||||
payRecord.setCreateTime(new Date()); |
||||
return payRecordMapper.insertSelective(payRecord); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,23 @@ |
||||
package com.hai.service.pay.impl; |
||||
|
||||
import com.hai.service.pay.PayService; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.Map; |
||||
|
||||
@Service(value = "testPayService") |
||||
public class TestPayService implements PayService { |
||||
|
||||
private static Logger log = LoggerFactory.getLogger(TestPayService.class); |
||||
|
||||
@Override |
||||
public void paySuccess(Map<String, String> map, String payType) throws Exception { |
||||
log.info("AlipayNotify Test --------> start"); |
||||
System.out.println("\nAlipayNotify Test --------> start"); |
||||
System.out.println("\nAlipayNotify Test --------> done"); |
||||
log.info("AlipayNotify Test --------> done"); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue